<?php
// +----------------------------------------------------------------------
// | 实体服务层
// +----------------------------------------------------------------------
// | 主要服务于实体数据处理
// +----------------------------------------------------------------------
// | 错误编码头 205xxx
// +----------------------------------------------------------------------
namespace common\service;

use common\model\DelayOrderTaskModel;
use common\model\EntityEpisodeRelationModel;
use common\model\OrderTaskListModel;
use common\model\ReviewFeedbackEntityModel;
use support\EntityTaskFilterParser;
use think\exception\HttpResponseException;
use common\model\NoteEntityRelationModel;
use common\exception\LogicException;
use common\model\StepCategoryModel;
use common\model\StepWorkflowModel;
use common\model\TaskMemberModel;
use common\model\StepSkuModel;
use common\model\ProjectModel;
use common\model\StatusModel;
use common\model\EntityModel;
use common\model\MediaModel;
use support\bootstrap\Redis;
use common\model\OrderModel;
use common\model\PlanModel;
use common\model\StepModel;
use common\model\TaskModel;
use common\model\TaskWorkOrderModel;
use common\model\UserModel;
use support\ErrorCode;
use support\SingletonTrait;
use think\module\Module;
use Webman\Stomp\Client;
use function request;
use think\Exception;
use Throwable;

class EntityService
{

    /**
     * @var EntityModel
     */
    private $entityModel;
    private $taskModel;


    // 单例工具
    use SingletonTrait;

    use EntityTaskFilterParser;

    public function __construct()
    {
        $this->entityModel = model(EntityModel::class);
        $this->taskModel = model(TaskModel::class);
    }

    /**
     * 创建实体
     * @param $param
     * @param $currentModuleData
     * @param $tenantId
     * @return array
     * @throws Exception
     * @throws Throwable
     */
    public function sampleCreate($param, $currentModuleData, $tenantId): array
    {
        // 获取项目模板配置
        $project = model(ProjectModel::class)->find($param['data']['project_id']);
        $templateService = TemplateService::getInstance();
        $moduleFieldConfig = $templateService->getProjectTemplateModuleFieldConfig($project['project_template_id']);
        $moduleFieldCheck = $moduleFieldConfig[$param['module_code']]['fields'] ?? [];
        foreach ($param['data'] as $key => $value) {
            if (!empty($moduleFieldCheck[$key]['validation_rules'])) {
                if (!empty($moduleFieldCheck[$key]['default_prefix'])) {
                    $value = str_replace($moduleFieldCheck[$key]['default_prefix'], "", $value);
                }
                $validateRules = validate_input_type($moduleFieldCheck[$key]['validation_rules'], $value);
                if ($validateRules['code'] == 0) {
                    $validationRulesString = implode(',', $validateRules['preg']);
                    throw new LogicException("Fields [{$key}] only allow " . $validationRulesString, ErrorCode::PROJECT_TEMPLATE_VALIDATE_RULE_ERROR);
                }
            }
        }
        // 实体model都应该传entity
        $commonService = CommonService::instance($currentModuleData['type']);

        //维护概念数据
        $maintainDesignData = $this->maintainDesignDataToScript($param, $currentModuleData['code']);
        if (!empty($maintainDesignData)) {
            return $maintainDesignData;
        }
        // 添加需求默认状态
        $defaultDemandStatus = StatusService::getInstance()->getOne(['code' => 'wait_demand'], '*');
        if (!empty($defaultDemandStatus)) {
            $param['data'][$currentModuleData['code'] . '_demand_status'] = $defaultDemandStatus['id'];
        }
        $entityModel = model(EntityModel::class);
        $entityModel->startTrans();
        try {
            switch ($currentModuleData['code']) {
                case "design":
//                    if (isset($param['data']['initial_episode_code'])) {
//                        $ep = $this->entityModel->where(['project_id' => $param['data']['project_id'], 'module_id' => Module::$moduleDictData['module_index_by_code']['episode']['id'], 'code' => $param['data']['initial_episode_code']])->find();
//                        if (!empty($ep)) {
//                            $param['data']['entity_id'] = $ep['id'];
//                            $param['data']['entity_module_id'] = $ep['module_id'];
//                            $param['data']['initial_episode_code'] = $ep['code'];
//                        }
//                    }

                    // 默认参数
                    break;
                case 'storyboard':
                    $noteRelateEpisodeList = model(NoteEntityRelationModel::class)->where(['project_id' => $param['data']['project_id'], 'episode_code' => $param['data']['initial_episode_code'], 'relation_type' => 'episode'])->find();
                    if ($noteRelateEpisodeList) {
                        $defaultDemandStatus = model(StatusModel::class)->where(['code' => 'already_relate'])->find();
                        if (!empty($defaultDemandStatus)) {
                            $param['data'][$currentModuleData['code'] . '_demand_status'] = $defaultDemandStatus['id'];
                        }
                    }
                case "sequence":
                    // 分镜,序列的entity_id entity_module_id 为剧集的id
                    $episodeCode = $param['data']['initial_episode_code'] ?? $param['data']['episode_name'] ?? '';
                    if (!empty($episodeCode)) {
                        $ep = $this->entityModel->where(['project_id' => $param['data']['project_id'], 'module_id' => Module::$moduleDictData['module_index_by_code']['episode']['id'], 'code' => $episodeCode])->find();
                        if (empty($ep)) {
                            // 不存在集数数据则生成，存在忽略
                            $categoryId = 0;
                            $stepCategory = model(StepCategoryModel::class)->where(['code' => 'episode'])->field('id')->find();
                            if (!empty($stepCategory)) {
                                $categoryId = $stepCategory['id'];
                            }
                            $stepWorkflowId = 0;
                            $episodeWorkflow = model(StepWorkflowModel::class)->where(['code' => 'episode'])->field('id')->find();
                            if (!empty($episodeWorkflow)) {
                                $stepWorkflowId = $episodeWorkflow['id'];
                            }
                            $ep = [
                                'name' => $episodeCode,
                                'code' => $episodeCode,
                                'tenant_id' => $tenantId,
                                'project_id' => $param['data']['project_id'],
                                'module_id' => Module::$moduleDictData['module_index_by_code']['episode']['id'],
                                'category_id' => $categoryId,
                                'initial_episode_code' => $episodeCode,
                                'step_workflow_id' => $stepWorkflowId,
                                'uuid' => create_uuid(),
                                'json' => json_encode([
                                    'episode_grade' => "A",
                                ]),
                            ];
                            $ep['id'] = $entityModel->add($ep);
                        }
                        $param['data']['entity_id'] = $ep['id'];
                        $param['data']['entity_module_id'] = $ep['module_id'];
                        $param['data']['initial_episode_code'] = $ep['code'];
                    }
                    break;
                case "shot":
                    // 镜头直接填充序列的数据
                case "asset":
                case "episode":
                case "level":
                default:
            }

            if (isset($param['data']['default_status_code'])) {
                $defaultStatus = model(StatusModel::class)->where(['code' => $param['data']['default_status_code']])->find();
                if (!empty($defaultStatus)) {
                    $param['data'][$currentModuleData['code'] . '_status'] = $defaultStatus['id'];
                }
            }

            // 支持media上传
            if (!empty($param['data']['media'])) {
                $mediaService = MediaService::getInstance();
                $mediaModel = model(MediaModel::class);
                $mediaIds = [];
                foreach ($param['data']['media'] as $mediaItem) {
                    if (empty($mediaItem)) {
                        continue;
                    }
                    $mediaService->generateMediaData($mediaItem);
                    $mediaData = $mediaModel->addItem($mediaItem);
                    if (!$mediaData) {
                        throw new Exception($mediaModel->getError(), ErrorCode::MEDIA_ADD_FAILURE);
                    } else {
                        $mediaIds[] = $mediaData['id'];
                    }
                }
                if (count($mediaIds) > 0) {
                    $param['data'][$currentModuleData['code'] . '_media'] = implode(',', $mediaIds);
                }
                unset($param['data']['media']);
            }
            $res = $commonService->commonCreate($param, $currentModuleData);
            $entityModel->commit();
            return $res;
        } catch (Throwable $e) {
            $entityModel->rollback();
            throw $e;
        }
    }

    /**
     * 删除实体
     * @param $param
     * @return array
     * @throws Throwable
     */
    public function delete($param)
    {
        /** @var EntityModel $entityModel */
        $entityModel = model(EntityModel::class);

        // 获取当前模块删除主键
        $masterIdData = $entityModel->field('id,is_assignee')->where($param["param"]['filter'])->select();

        if (empty($masterIdData)) {
            throw_strack_exception('Delete item does not exist.', ErrorCode::DELETE_ITEM_NOT_EXIST);
        }

        // 可以删除的实体
        $canDeleteIds = array_filter($masterIdData, function ($item) {
            return $item['is_assignee'] == "no";
        });
        $canDeleteIds = array_column($canDeleteIds, 'id');

        // 不能删除的实体
        $cantDeleteIds = array_filter($masterIdData, function ($item) {
            return $item['is_assignee'] == "yes";
        });
        $cantDeleteIds = array_column($cantDeleteIds, 'id');


        if (!empty($canDeleteIds)) {
            $entityModel->startTrans();
            try {
                $resData = $entityModel->deleteItem([
                    'id' => ["IN", $canDeleteIds],
                ]);
                if (!$resData) {
                    // 通用删除失败错误码 003
                    throw_strack_exception($entityModel->getError(), ErrorCode::COMMON_DELETE_FAILURE);
                }

                model(TaskModel::class)->deleteItem([
                    'entity_id' => ["IN", $canDeleteIds],
                ]);
                $entityModel->commit();
            } catch (Throwable $e) {
                $entityModel->rollback();
                throw $e;
            }
        }
        // 返回成功数据
        return ['ids' => $canDeleteIds, 'prohibit_deletion_ids' => $cantDeleteIds];
    }

    /**
     * 执行自动与概念关联关系
     * @param $projectId
     * @param $fromModuleId
     * @param $fromModuleCode
     * @param $toModuleId
     * @param $field
     * @throws \Exception
     */
    protected function doAutoRelateDesign($projectId, $fromModuleId, $fromModuleCode, $toModuleId, $field)
    {
        $entityModel = model(EntityModel::class);

        /**
         * 找出当前模块还未关联概念的模块
         * 1、asset_grade：资产等级和概念等级，design_grade
         * 2、asset_style：资产风格和概念风格，design_style
         * 3、asset_category：资产类型和概念类型，design_category
         *
         * 1、level_grade：关卡等级和概念等级，design_grade
         * 2、level_style：关卡风格和概念风格，design_style
         * 3、level_category：关卡类型和概念类型，design_category
         */
        $notRelatedEntityData = $entityModel
            ->field("
            id,
            name,
            code,
            json,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.{$fromModuleCode}_grade')) AS grade,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.{$fromModuleCode}_style')) AS style,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.{$fromModuleCode}_category')) AS category
            ")
            ->where([
                'project_id' => $projectId,
                'module_id' => $fromModuleId,
                '_string' => "JSON_EXTRACT(json, '$.{$field}' ) IS NULL",
            ])->select();

        if (!empty($notRelatedEntityData)) {
            /**
             * 查找出当前概念模块
             */
            $designEntityData = $entityModel
                ->field("
            id,
            name,
            code,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.design_grade')) AS grade,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.design_style')) AS style,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.design_category')) AS category
            ")
                ->where([
                    'project_id' => $projectId,
                    'module_id' => $toModuleId,
                ])->select();


            if (!empty($designEntityData)) {
                // 生成概念字典索引
                $designEntityDict = [];
                foreach ($designEntityData as $designEntityItem) {
                    $designEntityDict["{$designEntityItem['name']}_{$designEntityItem['grade']}_{$designEntityItem['style']}_{$designEntityItem['category']}"] = $designEntityItem['id'];
                }

                // 插入数据
                foreach ($notRelatedEntityData as $notRelatedEntityItem) {
                    $indexKey = "{$notRelatedEntityItem['name']}_{$notRelatedEntityItem['grade']}_{$notRelatedEntityItem['style']}_{$notRelatedEntityItem['category']}";
                    if (array_key_exists($indexKey, $designEntityDict)) {
                        $jsonArray = json_decode($notRelatedEntityItem['json'], true);
                        $jsonArray[$field] = '' . $designEntityDict[$indexKey];
                        $entityModel->where(['id' => $notRelatedEntityItem['id']])->setField('json', json_encode($jsonArray));
                    }
                }
            }
        }
    }

    /**
     * 自动与概念关联关系
     * 1、资产跟概念关联，asset_design
     * 2、关卡跟概念关联，level_design
     * @param $projectId
     * @return array
     * @throws \Exception
     */
    public function autoRelateDesign($projectId)
    {
        $assetModuleId = Module::$moduleDictData['module_index_by_code']['asset']['id'];
        $levelModuleId = Module::$moduleDictData['module_index_by_code']['level']['id'];
        $designModuleId = Module::$moduleDictData['module_index_by_code']['design']['id'];


        // 自动资产和概念关联
        $this->doAutoRelateDesign($projectId, $assetModuleId, 'asset', $designModuleId, 'asset_design');

        // 自动关卡和概念关联
        $this->doAutoRelateDesign($projectId, $levelModuleId, 'level', $designModuleId, 'level_design');

        return [];
    }

    /**
     * 增量更新集数id
     * @param int $projectId
     * @param array $needEpisodeList
     * @return array
     * @throws Exception
     */
    public function appendGenerateEpisodes($projectId = 0, $needEpisodeList = [])
    {
        $episodeModuleId = Module::$moduleDictData['module_index_by_code']['episode']['id'];
        $entityModel = model(EntityModel::class);

        $exitEpisodeData = $entityModel
            ->field('id,name,code')
            ->where([
                'project_id' => $projectId,
                'module_id' => $episodeModuleId,
            ])->select();

        $exitEpisodeCodeDict = [];
        $resData = [];

        if (!empty($exitEpisodeData)) {
            $exitEpisodeCodeDict = array_column($exitEpisodeData, 'id', 'code');
        }

        foreach ($needEpisodeList as $name) {
            // 避免集名称为英文 或者数字 导致名称和code不一致
            if (array_key_exists($name, $exitEpisodeCodeDict)) {
                $resData[$name] = $exitEpisodeCodeDict[$name];
            } else {
                $addItem = $entityModel->addItem([
                    'name' => $name,
                    'code' => $name,
                    'project_id' => $projectId,
                    'module_id' => $episodeModuleId,
                ]);
                if ($addItem === false && $entityModel->getErrorCode() !== ErrorCode::DATA_ALREADY_EXISTS) {
                    throw new Exception($entityModel->getError(), $entityModel->getErrorCode());
                } else if ($addItem === false && $entityModel->getErrorCode() == ErrorCode::DATA_ALREADY_EXISTS) {
                    $addItem = $entityModel->getCheckUniqueExitData();
                }

                $resData[$name] = $addItem['id'];
            }
        }

        return $resData;
    }

    /**
     * 查询一个未知类型的实体
     * @param $param
     * @return array|null
     */
    public function getUnknownEntity($param)
    {
        $queryFields = [
            'module.id', 'module.code',
            'entity.id', 'entity.name', 'entity.code', 'entity.initial_episode_code', 'entity.showings_number', 'entity.description', 'entity.json',
            'project.id', 'project.name', 'project.code',
        ];
        $entity = model(EntityModel::class)
            ->join('project on project.id = entity.project_id', 'LEFT')
            ->join('module on module.id = entity.module_id', 'LEFT')
            ->where($param['filter'])
            ->field(build_complex_query_fields($queryFields, '__'))
            ->find();
        if (empty($entity)) {
            return null;
        }
        $entity = separate_complex_query_data($entity, '__');
        $itemJsonFields = json_decode($entity['entity']['json'] ?? '{}', true);
        unset($entity['entity']['json']);
        $entity['entity'] = array_merge($itemJsonFields, $entity['entity']);
        return $entity;
    }

    /**
     * 检查序列里的镜头 属性是否合法
     * @param $episodeSequenceKey
     * @param $notGeneratedStoryboardItem
     * @param $storyboardAsset
     * @param $storyboardLevel
     * @param $sequenceEntityWorkflowParam
     */
    public function checkSequenceShotAttr($episodeSequenceKey, $notGeneratedStoryboardItem, $storyboardAsset, $storyboardLevel, &$sequenceEntityWorkflowParam)
    {
        if (!array_key_exists($episodeSequenceKey, $sequenceEntityWorkflowParam)) {
            $sequenceEntityWorkflowParam[$episodeSequenceKey] = [
                'sequence_name' => $notGeneratedStoryboardItem['sequence'],
                'production_method' => $notGeneratedStoryboardItem['production_method'],
                'cloth_solution' => $notGeneratedStoryboardItem['cloth_solution'],
                'episode_code' => $notGeneratedStoryboardItem['initial_episode_code'],
                'perform_form' => [$notGeneratedStoryboardItem['perform_form']], // 追加表演形式
                'perform_species' => [$notGeneratedStoryboardItem['perform_species']], // 追加表演种族
                'perform_numbers' => $notGeneratedStoryboardItem['perform_numbers'], // 取镜头最大值
                'style' => $notGeneratedStoryboardItem['style'], // 取镜头风格，必须统一
                'storyboard_level' => $storyboardLevel,
                'storyboard_asset' => $storyboardAsset,
                'duration' => (int)$notGeneratedStoryboardItem['duration'],
            ];
        } else {

            // 取镜头风格，必须统一
            if ($notGeneratedStoryboardItem['style'] > $sequenceEntityWorkflowParam[$episodeSequenceKey]['style']) {
                throw_strack_exception("Lens style must be unified.", ErrorCode::LENS_STYLE_MUST_BE_UNIFIED);
            }
        }

    }

    /**
     *
     * 分镜数据 自动处理名字关联
     * @param int $projectId
     * @param int $tenantId
     * @param array $filter
     * @return array[]
     * @throws Exception
     */
    public function autoTranslateAssetAndLevel(int $projectId, int $tenantId, $filter = []): array
    {

        // 1. 取出所有未生成后续任务分镜 is_generated_follow = no
        $entityModel = model(EntityModel::class);
        $entityFilter = [
            'project_id' => $projectId,
            'module_id' => Module::$moduleDictData['module_index_by_code']['storyboard']['id'],
            'tenant_id' => $tenantId,
            'is_generated_follow' => 'no',
        ];

        if (!empty($filter)) {
            $entityFilter['_complex'] = $filter;
        }

        $notGeneratedStoryboardData = $entityModel
            ->field("
            id,
            name,
            code,
            tenant_id,
            project_id,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_asset')) AS storyboard_asset,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_level')) AS storyboard_level
            ")
            ->where($entityFilter)
            ->select();

        $oldData = [];

        $assetNames = [];
        $levelNames = [];

        // 检索出需要修正的关卡和资产数据
        foreach ($notGeneratedStoryboardData as &$storyboardDatum) {
            $assetsFields = explode(',', $storyboardDatum['storyboard_asset']);
            $levelFields = explode(',', $storyboardDatum['storyboard_level']);

            $needChange = false;
            foreach ($assetsFields as $assetField) {
                if (!empty($assetField) && !is_numeric($assetField)) {
                    $needChange = true;
                    if (!in_array($assetField, $assetNames)) {
                        $assetNames[] = $assetField;
                    }
                }
            }

            foreach ($levelFields as $levelField) {
                if (!empty($levelField) && !is_numeric($levelField)) {
                    $needChange = true;
                    if (!in_array($levelField, $levelNames)) {
                        $levelNames[] = $levelField;
                    }
                }
            }
            if ($needChange) {
                $storyboardDatum['storyboard_asset'] = $assetsFields;
                $storyboardDatum['storyboard_level'] = $levelFields;
                $oldData[$storyboardDatum['id']] = $storyboardDatum;
            }
        }

        $assets = [];
        // 通过code检索对应的资产
        if (count($assetNames) > 0) {
            $assets = $this->entityModel
                ->where([
                    'tenant_id' => $tenantId,
                    'project_id' => $projectId,
                    'name' => ['IN', implode(',', $assetNames)],
                    'module_id' => Module::$moduleDictData['module_index_by_code']['asset']['id'],
                ])
                ->select();
            if (count($assets) > 0) {
                $assets = array_column($assets, null, 'name');
            }
        }

        $levels = [];
        // 通过code检索对应的关卡
        if (count($levelNames) > 0) {
            $levels = $this->entityModel
                ->where([
                    'tenant_id' => $tenantId,
                    'project_id' => $projectId,
                    'module_id' => Module::$moduleDictData['module_index_by_code']['level']['id'],
                    'name' => ['IN', implode(',', $levelNames)],
                ])
                ->select();
            if (count($levels) > 0) {
                $levels = array_column($levels, null, 'name');
            }
        }

        if (count($levels) > 0 || count($assets) > 0) {
            $commonService = CommonService::instance('entity');
            // 遍历需要更新的数据 进行更新
            foreach ($oldData as $entityId => &$fields) {
                $fields['new_storyboard_asset'] = array_map(function ($item) use ($assets) {
                    if (!empty($item) && !is_numeric($item) && isset($assets[$item])) {
                        $item = $assets[$item]['id'];
                    }
                    return $item;
                }, $fields['storyboard_asset']);

                $fields['new_storyboard_asset'] = implode(',', $fields['new_storyboard_asset']);
                $fields['storyboard_asset'] = implode(',', $fields['storyboard_asset']); // 合并数组field 便于对比
                $fields['new_storyboard_level'] = array_map(function ($item) use ($levels) {
                    if (!empty($item) && !is_numeric($item) && isset($levels[$item])) {
                        $item = $levels[$item]['id'];
                    }
                    return $item;
                }, $fields['storyboard_level']);

                $fields['new_storyboard_level'] = implode(',', $fields['new_storyboard_level']);
                $fields['storyboard_level'] = implode(',', $fields['storyboard_level']); // 合并数组field 便于对比

                $updateData = [];
                if ($fields['new_storyboard_asset'] != $fields['storyboard_asset']) {
                    $updateData['storyboard_asset'] = $fields['new_storyboard_asset'];
                }

                if ($fields['new_storyboard_level'] != $fields['storyboard_level']) {
                    $updateData['storyboard_level'] = $fields['new_storyboard_level'];
                }
                if (!empty($updateData)) {
                    $updateData['id'] = $entityId;
                    $commonService->update($updateData, 'storyboard');
                }
            }
        }

        return $oldData;
    }

    /**
     * 通过镜头查询分镜
     * @param $projectId
     * @param $episodeCode
     * @param $shotName
     * @param $sequenceName
     * @return array|false|mixed|string|null
     */
    public function findStoryboardByShot($projectId, $episodeCode, $shotName, $sequenceName)
    {
        return model(EntityModel::class)
            ->where([
                'name' => $shotName, // 镜头名称
                'project_id' => $projectId,
                'initial_episode_code' => $episodeCode, // 集名称
                '_string' => "JSON_UNQUOTE(JSON_EXTRACT(json,'$.storyboard_sequence')) = '{$sequenceName}'", // 序列名称
            ])
            ->find();
    }

    /**
     * 通过序列查询分镜
     * @param $projectId
     * @param $episodeCode
     * @param $sequenceName
     * @return array|false|mixed|string
     */
    public function selectStoryboardBySequence($projectId, $episodeCode, $sequenceName)
    {
        return model(EntityModel::class)
            ->where([
                'project_id' => $projectId,
                'initial_episode_code' => $episodeCode, // 集名称
                '_string' => "JSON_UNQUOTE(JSON_EXTRACT(json,'$.storyboard_sequence')) = '{$sequenceName}'", // 序列名称
            ])
            ->select();
    }

    /**
     * 查询关联的实体 结果包含自己
     * @param int $entityId
     * @return array|false|mixed|string
     */
    public function getRelatedEntities(int $entityId)
    {
        /**
         *
         * 概念,资产,关卡  通过概念生成的 code都会保持一致
         *
         * 序列,镜头,分镜  序列和镜头关联(entity_id) 序列和分镜关联   分镜和镜头关联
         *
         */
        $currentEntity = model(EntityModel::class)->where(['id' => $entityId])->find();
        if (empty($currentEntity)) {
            return [];
        }
        $relationEntities = [$currentEntity];
        switch (Module::$moduleDictData['module_index_by_id'][$currentEntity['module_id']]['code']) {
            case "asset":
            case "level":
                // 资产和关卡 查询关联的概念
                $relatedDesign = model(EntityModel::class)->where(['project_id' => $currentEntity['project_id'], 'code' => $currentEntity['code'], 'module_id' => Module::$moduleDictData['module_index_by_code']['design']['id']])->find();
                if (!empty($relatedDesign)) {
                    $relationEntities[] = $relatedDesign;
                }
                break;
            case "shot":
                // 镜头查询 序列和分镜的实体
                $relationSequenceEntity = model(EntityModel::class)->where(['id' => $currentEntity['entity_id']])->find();
                if (!empty($relationSequenceEntity)) {
                    $relationEntities = array_merge($relationEntities, [$relationSequenceEntity]);
                    // 分镜 集 序 镜头
                    $relationStoryboardEntity = $this->findStoryboardByShot($currentEntity['project_id'], $currentEntity['initial_episode_code'], $currentEntity['name'], $relationSequenceEntity['name']);
                    if (!empty($relationStoryboardEntity)) {
                        $relationEntities = array_merge($relationEntities, [$relationStoryboardEntity]);
                    }
                }
                break;
            case "sequence":
                // 序列查询 分镜

                // 分镜 集 序 镜头
                $relationStoryboardEntity = $this->selectStoryboardBySequence($currentEntity['project_id'], $currentEntity['initial_episode_code'], $currentEntity['name']);

                if (!empty($relationStoryboardEntity)) {
                    $relationEntities = array_merge($relationEntities, $relationStoryboardEntity);
                }
                break;
            case "design":
            case "storyboard":
            default:

        }

        return $relationEntities;
    }

    /**
     * 生成任务 一批实体
     * @param $filter
     * @param $moduleCode
     * @param $tenantId
     * @param string $entityStatus
     * @param string $taskDefaultStatus
     * @param array $stepCategoryTaskDefaultValue
     * @return array|array[]
     * @throws \Throwable
     * @throws \think\Exception
     */
    public function generateTask($filter, $moduleCode, $tenantId, string $entityStatus = 'task_generated', string $taskDefaultStatus = '', $stepCategoryTaskDefaultValue = [])
    {
        /**
         * 1. 匹配出当前应该使用的工序流
         * 2. 生成任务 && 标记状态
         */
        $entities = $this->entityModel->where($filter)->field('id,project_id')->select();
        if (empty($entities)) {
            throw_strack_exception('not match entity data', ErrorCode::MISSING_MATCH_ENTITY_DATA);
        }
        $projectId = $entities[0]['project_id'];
        $entityIds = implode(',', array_column($entities, 'id'));
        // 生成任务
        return TaskGenerateService::getInstance()->doGenerateEntityStepCategoryTask($projectId, $moduleCode, $entityIds, $tenantId, $entityStatus, $taskDefaultStatus, $stepCategoryTaskDefaultValue);
    }

    /**
     * 生成实体和任务 发布2
     * @param $filter
     * @param $moduleCode
     * @param $tenantId
     * @param string $entityStatus
     * @param string $taskDefaultStatus
     * @param array $stepCategoryTaskDefaultValue
     * @return array
     * @throws Exception
     * @throws Throwable
     */
    public function generateChildEntityAndTask($filter, $moduleCode, $tenantId, string $entityStatus = 'task_generated', string $taskDefaultStatus = '', $stepCategoryTaskDefaultValue = [])
    {
        /**
         * 1. 生成实体
         * 2.生成任务
         */
        $entity = $this->entityModel->where($filter)->find();

        $res = [];

        if ($moduleCode === 'design') {
            $res = EntityGenerateService::getInstance()->generateAssetAndLevelByDesign($entity['project_id'], $tenantId, $filter, $entityStatus, $taskDefaultStatus, $stepCategoryTaskDefaultValue);
        } else if ($moduleCode === 'storyboard') {
            $res = EntityGenerateService::getInstance()->generateShotByStoryboard($entity['project_id'], $tenantId, $filter, $entityStatus, $taskDefaultStatus, $stepCategoryTaskDefaultValue);
        } else {
            // todo sequence 会调用 不能报错
            // 其他的不支持生成后续流程实体
            // throw_strack_exception('only support [design,storyboard]', ErrorCode::DATA_INVALID);
        }
        return $res;
    }

    /**
     * 组装序列
     * @param $filter
     * @param $data
     * @return array
     * @throws \Exception
     */
    public function assemblySequence($filter, $data)
    {
        /**
         *
         * 1. 查找同序列的分镜数据
         * 2. 合并分镜数据
         * 3. 更新属性
         *
         */

        $fields = "
            id,
            name,
            code,
            tenant_id,
            project_id,
            duration,
            initial_episode_code,
            showings_number,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_sequence')) AS sequence,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_text_script')) AS text_script,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_cloth_solution')) AS cloth_solution,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_fx')) AS fx,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_illumination')) AS illumination,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_asset')) AS storyboard_asset,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_level')) AS storyboard_level,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_style')) AS style,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_type')) AS type,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_perform_species')) AS perform_species,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_perform_numbers')) AS perform_numbers,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_perform_form')) AS perform_form,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_production_method')) AS production_method
            ";
        // 要进行组装的分镜
        $filter['module_id'] = Module::$moduleDictData['module_index_by_code']['storyboard']['id'];
        $storyboardsWantBind = $this->entityModel->where($filter)->field($fields)->select();

        if (empty($storyboardsWantBind)) {
            throw_strack_exception('entity not found', ErrorCode::ENTITY_NOT_FOUND);
        }
        $tenantId = $storyboardsWantBind[0]['tenant_id'];
        $projectId = $storyboardsWantBind[0]['project_id'];
        $episodeCode = $data['initial_episode_code'];

        // 检查要组装的序列是否存在


        $sequenceExist = $this->entityModel->where([
            'module_id' => Module::$moduleDictData['module_index_by_code']['storyboard']['id'],
            'project_id' => $projectId,
            'initial_episode_code' => $episodeCode,
            "json_unquote(json_extract(json,'$.storyboard_sequence'))" => $data['sequence_name'],
            "showings_number" => ['neq', $data['showings_number']],
        ])->field("id,showings_number")->find();

        if (!empty($sequenceExist)) {
            throw new LogicException("sequence already exist in  {$episodeCode} {$sequenceExist['showings_number']}", ErrorCode::SEQUENCE_ALREADY_EXIST);
        }


        // 集信息

        $episode = $this->entityModel->where(['module_id' => Module::$moduleDictData['module_index_by_code']['episode']['id'], 'project_id' => $projectId, 'code' => $episodeCode])->find();
        if (empty($episode)) {
            throw_strack_exception('missing episode', ErrorCode::MISSING_EPISODE);
        }
        // 查询同一集 同一场 同一序 的所有分镜
        $oldStoryBoards = $this
            ->entityModel
            ->where([
                'module_id' => Module::$moduleDictData['module_index_by_code']['storyboard']['id'],
                'project_id' => $episode['project_id'],
                'initial_episode_code' => $episode['code'],
            ])
            ->where("JSON_UNQUOTE(JSON_EXTRACT(json,'$.storyboard_sequence')) = '{$data['sequence_name']}' AND  showings_number = '{$data['showings_number']}'")
            ->field($fields)
            ->select();

        // diff 计算出需要删除序列和组装序列的数据
        $newIdList = array_column($storyboardsWantBind, 'id');
        $needRemoved = [];
//        $needAdded = $newIdList;

        if (count($oldStoryBoards) > 0) {
            $oldIdList = array_column($oldStoryBoards, 'id');
            $needRemoved = array_diff($oldIdList, $newIdList);
//            $needAdded = array_diff($newIdList, $oldIdList);
        }
        $storyBoards = array_merge($storyboardsWantBind, $oldStoryBoards);
        $commonService = CommonService::instance('entity');
        $waitForGenerateTask = model(StatusModel::class)->where(['code' => 'wait_for_generated'])->find();
        $waitForGenerateMergeSequence = model(StatusModel::class)->where(['code' => 'wait_for_merge_sequence'])->find();
        $res = [];
        $commonService->setCurrentModuleCode('storyboard');
        // 遍历更新分镜的序列信息和制作方式,布料解算 实体状态为待生成任务
        foreach ($storyBoards as $storyBoard) {
            $updateData = [
                'id' => $storyBoard['id'],
                'storyboard_production_method' => $data['production_method'],
                'storyboard_cloth_solution' => $data['cloth_solution'],
                'storyboard_motion_sequence' => '', // 组装序列和动捕序列只允许存在一个
            ];
            if (in_array($storyBoard['id'], $needRemoved)) {
                $sequenceName = "";
                $updateData['description'] = '';
                $updateData['storyboard_status'] = $waitForGenerateMergeSequence['id'];
            } else {
                $sequenceName = $data['sequence_name'];
                if (isset($data['description'])) {
                    $updateData['description'] = $data['description'];
                }
                $updateData['storyboard_status'] = $waitForGenerateTask['id'];
            }
            $updateData['storyboard_sequence'] = $sequenceName;
            $res[] = $commonService->update($updateData, 'storyboard', false);

        }
        return $res;
    }

    /**
     * 更新序列
     * @param $data
     * @return array
     * @throws Exception
     */
    public function updateSequence($data)
    {
        /**
         * 1. 查找同序列的分镜数据
         * 2. 合并分镜数据
         * 3. 更新属性
         */

        $fields = "
            id,
            name,
            code,
            tenant_id,
            project_id,
            duration,
            initial_episode_code,
            showings_number,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_sequence')) AS sequence,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_text_script')) AS text_script,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_cloth_solution')) AS cloth_solution,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_fx')) AS fx,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_illumination')) AS illumination,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_asset')) AS storyboard_asset,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_level')) AS storyboard_level,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_style')) AS style,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_type')) AS type,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_perform_species')) AS perform_species,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_perform_numbers')) AS perform_numbers,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_perform_form')) AS perform_form,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_production_method')) AS production_method
            ";

        $projectId = $data['project_id'];
        $episodeCode = $data['initial_episode_code'];
        // 集信息
        $episode = $this->entityModel->where(['module_id' => Module::$moduleDictData['module_index_by_code']['episode']['id'], 'project_id' => $projectId, 'code' => $episodeCode])->find();
        if (empty($episode)) {
            throw_strack_exception('missing episode', ErrorCode::MISSING_EPISODE);
        }
        // 查询同一集 同一场 同一序 的所有分镜
        $storyBoards = $this
            ->entityModel
            ->where([
                'module_id' => Module::$moduleDictData['module_index_by_code']['storyboard']['id'],
                'project_id' => $episode['project_id'],
                'initial_episode_code' => $episode['code'],
            ])
            ->where("JSON_UNQUOTE(JSON_EXTRACT(json,'$.storyboard_sequence')) = '{$data['sequence_name']}' AND showings_number = '{$data['showings_number']}'")
            ->field($fields)
            ->select();


        if (count($storyBoards) === 0) {
            return [];
        }

        $commonService = CommonService::instance('entity');
        $waitForGenerateTask = model(StatusModel::class)->where(['code' => 'wait_for_generated'])->find();
        $res = [];
        $commonService->setCurrentModuleCode('storyboard');

        // 遍历更新分镜的序列信息和制作方式,布料解算 实体状态为待生成任务
        foreach ($storyBoards as $storyBoard) {
            $sequenceName = $data['sequence_name'];
            $updateData['storyboard_sequence'] = $sequenceName;
            $updateData = [
                'id' => $storyBoard['id'],
                'storyboard_sequence' => $sequenceName,
                'storyboard_production_method' => $data['production_method'],
                'storyboard_cloth_solution' => $data['cloth_solution'],
                'storyboard_status' => $waitForGenerateTask['id'],
            ];
            $res[] = $commonService->update($updateData, 'storyboard', false);

        }
        return $res;
    }

    /**
     * 更新实体媒体信息
     * @param $data
     * @param string $moduleCode
     * @return array|bool
     * @throws \Exception
     */
    public function updateEntityMediaInfo($data, string $moduleCode)
    {
        $updateField = $moduleCode . '_media';
        $mediaService = MediaService::getInstance();
        $mediaModel = model(MediaModel::class);
        $data['medias'] = $data['medias'] ?? [];
        $updateData = [
            'id' => $data['id'],
            $updateField => '',
        ];
        $mediaIds = [];
        foreach ($data['medias'] as $mediaItem) {
            $mediaId = $mediaItem['id'] ?? 0;
            if ($mediaId === 0) {
                $mediaService->generateMediaData($mediaItem);
                $mediaData = $mediaModel->addItem($mediaItem);
                if (!$mediaData) {
                    throw new Exception($mediaModel->getError(), ErrorCode::MEDIA_ADD_FAILURE);
                } else {
                    $mediaId = $mediaData['id'];
                }
            }

            $mediaIds[] = $mediaId;
        }
        $updateData[$updateField] = implode(',', $mediaIds);
        $commonService = CommonService::instance('entity');
        return $commonService->update($updateData, $moduleCode, false);
    }

    /**
     * 发布实体 (生成任务)
     * @param $filter
     * @param string $getModuleCode
     * @param $tenantId
     * @param string $entityStatus
     * @param string $taskDefaultStatus
     * @param array $stepCategoryTaskDefaultValue
     * @return array
     * @throws Exception
     * @throws Throwable
     */
    public function publish($filter, string $getModuleCode, $tenantId, string $entityStatus = 'task_generated', string $taskDefaultStatus = '', $stepCategoryTaskDefaultValue = [])
    {
        $res = [];
        // 查询任务是否废除
        $statusClosed = StatusService::getInstance()->getTaskDefaultClosedStatus();
        // 没有废除的任务
        $notAbolitionIds = [];
        $entities = $this->entityModel->where($filter)->select();
        $notAbolitionEntities = [];
        $designOrStoryboardModule = [module_by_code('design')['id'], module_by_code('storyboard')['id']];
        $designOrStoryboardList = [];
        foreach ($entities as $item) {
            $item['json'] = json_decode($item['json'], true);
            // 任务废除跳过filter
            if (!empty($item['json'][$getModuleCode . '_status']) && $item['json'][$getModuleCode . '_status'] == $statusClosed['id']) {
                continue;
            }
            if (in_array($item['module_id'], $designOrStoryboardModule)) {
                $designOrStoryboardList[] = $item;
            }
            $notAbolitionIds[] = $item['id'];
            $notAbolitionEntities[] = $item;
        }
        if (empty($notAbolitionIds)) {
            return [];
        }
        $filter = ['entity.id' => ['IN', join(',', $notAbolitionIds)]];

        // publish lock
        $lockKey = 'lock_entity_publish:' . md5(json_encode($filter));
        if (Redis::get($lockKey)) {
            throw new LogicException("Publishing is currently in progress", ErrorCode::PUBLISHING_IS_CURRENTLY_IN_PROGRESS);
        } else {
            Redis::setEx($lockKey, 60, json_encode($filter));
        }

        $this->entityModel->startTrans();
        try {
            foreach ($notAbolitionIds as $id) {
                Redis::setEx('entity_publishing' . $id, 60, 1);
            }

            $this->fillEntitiesDefaultAttr($notAbolitionEntities, $getModuleCode);
            $res['current_entity'] = $this->generateTask($filter, $getModuleCode, $tenantId, $entityStatus, $taskDefaultStatus, $stepCategoryTaskDefaultValue);
            $res['child_entity'] = $this->generateChildEntityAndTask($filter, $getModuleCode, $tenantId, $entityStatus, $taskDefaultStatus, $stepCategoryTaskDefaultValue);
            $this->entityModel->commit();
        } catch (Throwable $e) {
            $this->entityModel->rollback();
            throw $e;
        } finally {
            $delKeys = [$lockKey];
            foreach ($notAbolitionIds as $id) {
                $delKeys[] = 'entity_publishing' . $id;
            }
            Redis::del($delKeys);
        }
        foreach ($designOrStoryboardList as $designOrStoryboard) {
            Client::send('check_entity_status', build_queue_data_with_xu(['entity_id' => $designOrStoryboard['id']]), 2);
        }
        //更新需求池
        Client::send('sync_demand_pool_report', build_queue_data_with_xu(['entity_ids' => $notAbolitionIds]));
        return $res;
    }


    /**
     * 发布实体 (生成任务V3)
     * @param $filter
     * @param string $getModuleCode
     * @param $tenantId
     * @param string $entityStatus
     * @param array $stepCategoryTaskDefaultValue
     * @return array
     * @throws Exception
     * @throws Throwable
     */
    public function publishV3($filter, string $getModuleCode, $tenantId, string $entityStatus = 'task_generated', $stepCategoryTaskDefaultValue = [])
    {
        $res = [];
        // 查询任务是否废除
        $statusClosed = StatusService::getInstance()->getTaskDefaultClosedStatus();
        // 没有废除的任务
        $notAbolitionIds = [];
        $entities = $this->entityModel->where($filter)->select();

        $notAbolitionEntities = [];
        $assetOrShotModule = [module_by_code('asset')['id'], module_by_code('shot')['id']];
        $assetOrShotList = [];
        foreach ($entities as $item) {
            $item['json'] = json_decode($item['json'], true);
            // 任务废除跳过filter
            if (!empty($item['json'][$getModuleCode . '_status']) && $item['json'][$getModuleCode . '_status'] == $statusClosed['id']) {
                continue;
            }
            if (in_array($item['module_id'], $assetOrShotModule)) {
                $assetOrShotList[] = $item;
            }
            $notAbolitionIds[] = $item['id'];
            $notAbolitionEntities[] = $item;
        }

        if (empty($notAbolitionIds)) {
            return [];
        }
        $filter = ['entity.id' => ['IN', join(',', $notAbolitionIds)]];

        // publish lock
        $lockKey = 'lock_entity_publish:' . md5(json_encode($filter));
        if (Redis::get($lockKey)) {
            throw new LogicException("Publishing is currently in progress", ErrorCode::PUBLISHING_IS_CURRENTLY_IN_PROGRESS);
        } else {
            Redis::setEx($lockKey, 60, json_encode($filter));
        }

        foreach ($notAbolitionIds as $id) {
            Redis::setEx('entity_publishing' . $id, 60, 1);
        }

        $this->entityModel->startTrans();
        try {
            $this->fillEntitiesDefaultAttr($notAbolitionEntities, $getModuleCode);
            $tmpEntity = $this->entityModel->where($filter)->find();
            $generateTaskV3 = EntityGenerateService::getInstance()->generateEntityTask($tmpEntity['project_id'], $tenantId, $filter, $entityStatus, $stepCategoryTaskDefaultValue);
            $res['child_entity'] = $generateTaskV3;
            $this->entityModel->commit();
        } catch (Throwable $e) {
            $this->entityModel->rollback();
            throw $e;
        } finally {
            $delKeys = [$lockKey];
            foreach ($notAbolitionIds as $id) {
                $delKeys[] = 'entity_publishing' . $id;
            }
            Redis::del($delKeys);
        }
        foreach ($assetOrShotList as $assetOrShotListData) {
            Client::send('check_entity_status', build_queue_data_with_xu(['entity_id' => $assetOrShotListData['id']]), 2);
        }
        return $res;
    }

    /**
     * 获取实体默认属性
     * @param $attrList
     * @param $moduleCode
     * @return array
     */
    public function getEntityDefaultAttrValue($attrList, $moduleCode)
    {
        if (empty($moduleCode)) {
            return [];
        }
        $moduleDefaultAttr = ModuleService::getInstance()->getModuleDefaultAttr($moduleCode);
        if (empty($moduleDefaultAttr)) {
            return [];
        }
        return EntityService::getInstance()->mergeJsonFieldWithDefaultAttr($attrList, $moduleDefaultAttr);
    }

    /**
     * 提交 编剧提交给创意 修改状态为待二次补充状态
     * @param $filter
     * @param string $getModuleCode
     * @return array
     * @throws \Exception
     */
    public function submit($filter, string $getModuleCode)
    {
        // 只有第一次补充完毕才能提交到待第二次补充
        $firstAddFinished = model(StatusModel::class)->where(['code' => 'first_add_finished'])->find();
        $filter[] = ["JSON_UNQUOTE(JSON_EXTRACT(json,'$." . $getModuleCode . "_status'))" => $firstAddFinished['id']];

        $entities = $this->entityModel->where($filter)->select();
        $commonService = CommonService::instance('entity');
        $commonService->setCurrentModuleCode($getModuleCode);
        $waitForSecondAddStatus = model(StatusModel::class)->where(['code' => 'wait_for_second_add'])->find();
        if (empty($waitForSecondAddStatus)) {
            throw_strack_exception('missing status config [wait_for_second_add]', ErrorCode::MISSING_STATUS);
        }
        $res = [];
        foreach ($entities as $entity) {
            $res[$entity['id']] = $commonService->update([
                'id' => $entity['id'],
                "{$getModuleCode}_status" => $waitForSecondAddStatus['id'],
            ], $getModuleCode, false);
        }
        return $res;
    }


    /**
     * 概念状态自动流转
     * @param $data
     */
    public function designStatusAutoChange($data)
    {
        $currentEntity = $this->entityModel->find($data['id']);

        /**
         * 通过当前实体的 状态 来判断是要流转到哪一个状态
         */

        $entityJsonFields = json_decode($currentEntity['json'], true);

        $entityStatusId = $entityJsonFields['design_status'] ?? 0;

        $statusList = model(StatusModel::class)->select();
        $statusList = array_column($statusList, null, 'id');

        $statusCodeMap = array_column($statusList, 'id', 'code');
        $currentStatus = $statusList[$entityStatusId] ?? ['code' => null];
        $optionService = OptionsService::getInstance();

        $newEntityData = array_merge($currentEntity, $entityJsonFields, $data);
        switch ($currentStatus['code']) {
            case null:
            case 'wait_for_add':
            case 'first_add_finished':
                /**
                 * 第一次补充环节 数据检查
                 * 如果配置的字段都填充好了 那么就进入第一次补充完毕 如果没有填充好 就回到待补充
                 */
                $firstAddFinishedRules = $optionService->getOptionsConfigItemData('design_status_change_fields_rule', 'first_add_finished');
                $firstAddFinishedRules = explode(',', $firstAddFinishedRules);

                if (empty($firstAddFinishedRules)) {
                    if ($currentStatus['code'] !== 'first_add_finished') {
                        $data['design_status'] = $statusCodeMap['first_add_finished'];
                    }
                } else {
                    $firstAddFinished = $this->checkDesignFieldsFilled($firstAddFinishedRules, $newEntityData);
                    if ($firstAddFinished) {
                        if ($currentStatus['code'] !== 'first_add_finished') {
                            $data['design_status'] = $statusCodeMap['first_add_finished'];
                        }
                    } else {
                        if ($currentStatus['code'] !== 'wait_for_add') {
                            $data['design_status'] = $statusCodeMap['wait_for_add'];
                        }
                    }
                }
                break;
            case 'wait_for_second_add':
            case 'second_add_finished':
                /**
                 * 第二次补充环节  数据检查
                 *
                 * 如果配置的字段都填充好了 那么就进入第二次补充完毕 如果没有填充好 就回到待第二次补充
                 */
                $secondAddFinishedRules = $optionService->getOptionsConfigItemData('design_status_change_fields_rule', 'second_add_finished');
                $secondAddFinishedRules = explode(',', $secondAddFinishedRules);

                if (empty($secondAddFinishedRules)) {
                    if ($currentStatus['code'] !== 'second_add_finished') {
                        $data['design_status'] = $statusCodeMap['second_add_finished'];
                    }
                } else {
                    $secondAddFinished = $this->checkDesignFieldsFilled($secondAddFinishedRules, $newEntityData);

                    if ($secondAddFinished) {
                        if ($currentStatus['code'] !== 'second_add_finished') {
                            $data['design_status'] = $statusCodeMap['second_add_finished'];
                        }
                    } else {
                        if ($currentStatus['code'] !== 'wait_for_second_add') {
                            $data['design_status'] = $statusCodeMap['wait_for_second_add'];
                        }
                    }
                }
                break;
            default:
        }
        return $data;
    }

    /**
     * 检查是否提交要求的字段
     * @param array $needFields
     * @param $data
     * @return bool
     */
    public function checkDesignFieldsFilled(array $needFields, $data)
    {
        $totalFields = Module::$moduleDictData['field_index_by_code'][Module::$moduleDictData['module_index_by_id'][$data['module_id']]['code']];
        $fields = array_merge(array_keys($totalFields['fixed'] ?? []), array_keys($totalFields['custom'] ?? []));
        $documentRelationService = DocumentRelationService::getInstance();

        foreach ($needFields as $field) {
            if (empty($field)) {
                continue;
            }

            if ($field === 'doc') {

                $dr = $documentRelationService->findDocumentRelation([
                    'relation_type' => 'task_requirements',
                    'project_id' => $data['project_id'],
                    'attr_str' => $data['name'],
                ]);

                if (empty($dr)) {
                    return false;
                }

                $post = $documentRelationService->getDocumentByPostId($dr['post_id']);
                if (empty($post) || empty($post['content'])) {
                    return false;
                }
            }

            if (!in_array($field, $fields)) {
                // 表里没有这个字段 就跳过检查
                continue;
            }
            if (!array_key_exists($field, $data)) {
                return false;
            }
            if (empty($data[$field])) {
                return false;
            }
        }
        return true;
    }

    /**
     * 批量导入实体
     * @param array $dataList
     * @param string $moduleCode
     * @return array|array[]
     * @throws Exception
     */
    public function import(array $dataList, string $moduleCode)
    {
        $commonService = CommonService::instance('entity');
        $commonService->setCurrentModuleCode($moduleCode);
        $currentModuleData = Module::$moduleDictData['module_index_by_code'][$moduleCode] ?? null;

        $res = [
        ];

        foreach ($dataList as $dataIndex => $data) {
            try {
                unset($data['id'], $data['episode_ids']);
                // 不抛出已存在异常
                $result = $commonService->commonCreate(['data' => $data], $currentModuleData, false);
                $res[$dataIndex] = ['success' => true, 'name' => $data['name'], 'result' => $result];
            } catch (HttpResponseException $e) {
                $responseData = $e->getResponse()->getData();
                $res[$dataIndex] = [
                    'success' => false,
                    'name' => $data['name'],
                    'result' => [
                        'code' => $responseData['code'] ?? 0,
                        'msg' => $responseData['msg'] ?? $e->getMessage(),
                    ],
                ];
            } catch (Throwable $e) {
                $res[$dataIndex] = [
                    'success' => false,
                    'name' => $data['name'],
                    'result' => [
                        'code' => $e->getCode(),
                        'msg' => $e->getMessage(),
                    ],
                ];
            }
        }
        return $res;
    }

    /**
     * 移除分镜序列
     * @param array $param
     * @param int $tenantId
     * @return array
     * @throws \Exception
     */
    public function removeSequence(array $param, int $tenantId)
    {
        $res = [];
        $fields = "
            id,
            name,
            code,
            tenant_id,
            project_id,
            duration,
            initial_episode_code,
            showings_number,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_sequence')) AS sequence,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_text_script')) AS text_script,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_cloth_solution')) AS cloth_solution,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_fx')) AS fx,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_illumination')) AS illumination,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_asset')) AS storyboard_asset,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_level')) AS storyboard_level,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_style')) AS style,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_type')) AS type,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_perform_species')) AS perform_species,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_perform_numbers')) AS perform_numbers,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_perform_form')) AS perform_form,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_production_method')) AS production_method
            ";
        // 要进行组装的分镜
        $filter = $param['filter'];
        $filter['tenant_id'] = $tenantId;
        $filter['module_id'] = Module::$moduleDictData['module_index_by_code']['storyboard']['id'];
        $storyboardsWantUnBind = $this->entityModel->where($filter)->field($fields)->select();

        if (empty($storyboardsWantUnBind)) {
            throw_strack_exception('entity not found', ErrorCode::ENTITY_NOT_FOUND);
        }

        $commonService = CommonService::instance('entity');
        $commonService->setCurrentModuleCode('storyboard');
        $waitForMergeSequenceStatus = model(StatusModel::class)->where(['code' => 'wait_for_merge_sequence'])->find();
        // 移除分镜的序列字段
        foreach ($storyboardsWantUnBind as $storyboard) {
            $updateData = [
                'id' => $storyboard['id'],
                'storyboard_sequence' => '',
                'storyboard_production_method' => '',
                'storyboard_cloth_solution' => '',
                'storyboard_status' => $waitForMergeSequenceStatus['id'],
                'description' => '',
            ];
            $res[] = $commonService->update($updateData, 'storyboard', false);
        }
        return $res;
    }

    /**
     * 查询实体列表
     * @param $param
     * @param string $getModuleCode
     * @param bool $needTotal
     * @return array
     */
    public function getList($param, string $getModuleCode, bool $needTotal = true)
    {
        $entityFilter = $param['filter'] ?? [];
        $entityJsonFilter = [];
        $entityEpisodeRelationFilter = $param['entity_episode_relation_filter'] ?? [];
        $isRelationQuery = $param['is_relation_query'] ?? 'no';
        $entityFilter[$getModuleCode . '.module_id'] = Module::$moduleDictData['module_index_by_code'][$getModuleCode]['id'];
//        $entityFilter[] = [$getModuleCode . '.tenant_id' => $getTenantId];
        $projectId = $param['project_id'] ?? $entityFilter[$getModuleCode . '.project_id'] ?? $entityFilter['project_id'] ?? null;
        $episodeType = $param['episode_type'] ?? 'episode_make'; //默认本集制作
        $initialEpisodeCodeList = $param['initial_episode_code_list'] ?? null;
        $demandOrderId = $param['demand_order_id'] ?? null;
        $delayOrderId = $param['delay_order_id'] ?? null;

        $episodeSql = "";
        if (isset($projectId) && isset($initialEpisodeCodeList) && isset($episodeType)) {
            $entityFilter[$getModuleCode . '.project_id'] = $projectId;
            $episodeSql = $this->episodeFormatQuery($initialEpisodeCodeList, $episodeType, $projectId);
        }

        //需求单id
        if (!empty($demandOrderId)) {
            $reviewFeedbackEntityIds = model(ReviewFeedbackEntityModel::class)
                ->where(['review_feedback_entity.review_feedback_id' => $demandOrderId])
                ->field("entity_id")
                ->column("entity_id");
            if (!empty($reviewFeedbackEntityIds)) {
                $reviewFeedbackEntityIds = array_unique($reviewFeedbackEntityIds);
                $entityFilter[] = [
                    $getModuleCode . '.id' => ["IN", $reviewFeedbackEntityIds],
                ];
            } else {
                $entityFilter = [$getModuleCode . '.id' => 0];
            }
        }

        //延期单id
        if (!empty($delayOrderId)) {
            $delayOrderEntityList = model(DelayOrderTaskModel::class)
                ->join("task on task.id = delay_order_task.task_id")
                ->join("entity on entity.id = task.entity_id")
                ->where(['delay_order_id' => $delayOrderId])
                ->field("entity.id,entity.module_id,entity.json")
                ->select();
            if (!empty($delayOrderEntityList)) {
                $delayOrderEntityIds = [];
                foreach ($delayOrderEntityList as $delayOrderEntityItem) {
                    $moduleCode = module_by_id($delayOrderEntityItem['module_id'])['code'];
                    $delayOrderEntityJson = json_decode($delayOrderEntityItem['json'], true);
                    $delayOrderEntityId = $delayOrderEntityItem['id'];
                    if ($getModuleCode == 'design' && in_array($moduleCode, ['level', 'asset']) && !empty($delayOrderEntityJson[$moduleCode . '_design'])) {
                        $delayOrderEntityId = intval($delayOrderEntityJson[$moduleCode . '_design']);
                    } elseif ($getModuleCode == 'storyboard' && $moduleCode == 'shot' && !empty($delayOrderEntityJson[$moduleCode . '_storyboard'])) {
                        $delayOrderEntityId = intval($delayOrderEntityJson[$moduleCode . '_storyboard']);
                    }
                    $delayOrderEntityIds[$delayOrderEntityId] = $delayOrderEntityId;
                }
                $delayOrderEntityIds = array_values($delayOrderEntityIds);
                $entityFilter[] = [
                    $getModuleCode . '.id' => ["IN", $delayOrderEntityIds],
                ];
            } else {
                $entityFilter = [$getModuleCode . '.id' => 0];
            }
        }

        $joinFields = $param['fields'] ?? "*";
        $mediaFields = $param['media_fields'] ?? "";
        $taskFields = $param['task_fields'] ?? "";
        $order = $param['order'] ?? "{$getModuleCode}.initial_episode_code ASC,{$getModuleCode}.showings_number ASC,{$getModuleCode}.name ASC";
        $storyboardSequence = $param['storyboard_motion_sequence_required'] ?? 'no';

        $order = transform_normal_order($order, $getModuleCode);

        $page = $param['page'] ?? [1, 20];
        $groupBy = $param['group_by'] ?? '';
        if ($groupBy) {
            $groupByList = explode(',', $groupBy);
            foreach ($groupByList as &$groupBy) {
                $groupBy = transform_custom_field($groupBy, $getModuleCode);
            }
            unset($groupBy);
            $groupBy = implode(',', $groupByList);
        }

        if (isset($entityFilter['design.is_relate'])) {
            if ($entityFilter['design.is_relate']) {
                $entityJsonFilter = [
                    "JSON_UNQUOTE(JSON_EXTRACT(`design`.`json`,'$.design_relate_level'))" => [
                        ['exp', 'is not null'],
                        ['neq', ''],
                    ],
                ];
            } else {
                $entityJsonFilter = [
                    [[
                        "JSON_UNQUOTE(JSON_EXTRACT(`design`.`json`,'$.design_relate_level'))" => [
                            ['exp', 'is null'],
                        ],
                    ],
                        '_logic' => 'or',
                        "JSON_UNQUOTE(JSON_EXTRACT(`design`.`json`,'$.design_relate_level'))" => [
                            ['eq', ''],
                        ],
                    ],
                    "JSON_UNQUOTE(JSON_EXTRACT(`design`.`json`,'$.design_category'))" => [
                        ['IN', ['场景']],
                    ],
                ];
            }
            unset($entityFilter['design.is_relate']);
        }

        // 转化json字段的筛选
        $entityFilter = parse_filter_to_tp_where($entityFilter, $getModuleCode);
        if ($entityJsonFilter) {
            $entityFilter[] = $entityJsonFilter;
        }
        $joinFields = explode(',', $joinFields);
        $keySep = '__';
        $primaryKey = "{$getModuleCode}.id";
        $primaryKeyExpend = "{$getModuleCode}{$keySep}id";
        $mediaRelationField = "{$getModuleCode}.{$getModuleCode}_media";
        $mediaRelationKeyExpend = "{$getModuleCode}{$keySep}{$getModuleCode}_media";
        $storyboardSequenceRelationField = "{$getModuleCode}.{$getModuleCode}_motion_sequence";
        $storyboardSequenceRelationKeyExpend = "{$getModuleCode}{$keySep}{$getModuleCode}_motion_sequence";
        $getModuleCodeTaskField = "{$getModuleCode}.{$getModuleCode}_task";
        $getModuleCodeTaskRelationKeyExpend = "{$getModuleCode}{$keySep}{$getModuleCode}_task";

        $needQueryMedia = false;

        // 如果需要查询媒体 那么追加媒体关联字段与模块主键
        if (!empty($mediaFields)) {
            if (!in_array($primaryKey, $joinFields)) {
                $joinFields[] = $primaryKey;
            }
            if (!in_array($mediaRelationField, $joinFields)) {
                $joinFields[] = $mediaRelationField;
            }
            $needQueryMedia = true;
        }

        if (!in_array($getModuleCode . '.project_id', $joinFields)) {
            $joinFields[] = $getModuleCode . '.project_id';
        }
        if ($storyboardSequence === 'yes') {
            if (!in_array($storyboardSequenceRelationField, $joinFields)) {
                $joinFields[] = $storyboardSequenceRelationField;
            }
        }

        //  优化status查询字段
        $statusFields = [];
        $needTask = false;
        $needStatus = false;
        $needDemandStatus = false;
        $needSequence = false;
        $needShotAssetLevelQuery = false;
        $sequenceFields = [];
        $relatedModuleQueryFields = [];
        $isShot = $getModuleCode === 'shot';
        $shotRelateModules = [
            'asset',
            'level',
        ];
        if (!empty($taskFields)) {
            $needTask = true;
        }
        foreach ($joinFields as $key => $field) {
            $expField = explode('.', $field);
            if (isset($expField[0]) && $expField[0] === 'status' && $expField[1] !== 'id') {
                $statusFields[] = $expField[1];
                $needStatus = true;
                unset($joinFields[$key]);
            }

            if (isset($expField[1]) && $expField[1] === $getModuleCode . '_demand_status') {
                $needDemandStatus = true;
            }

            if (isset($expField[0]) && $expField[0] === 'sequence' && $isShot) {
                $sequenceFields[] = $expField[1];
                $needSequence = true;
                unset($joinFields[$key]);
            }
            if (isset($expField[0]) && in_array($expField[0], $shotRelateModules) && $isShot) {
                $relatedModuleQueryFields[] = transform_custom_field($field, $expField[0], 'sub') . ' AS ' . $expField[1];
                $needShotAssetLevelQuery = true;
                unset($joinFields[$key]);
            }
        }

        //分镜序列查询,追加entity_id
        if ($needSequence) {
            $joinFields[] = "{$getModuleCode}.entity_id";
        }
        if ($needTask) {
            $joinFields[] = $getModuleCodeTaskField;
        }
        $statusField = "{$getModuleCode}_status";
        if (!in_array("{$getModuleCode}.{$statusField}", $joinFields)) {
            $joinFields[] = "{$getModuleCode}.{$statusField}";
        }
        $outsourceStatusField = "{$getModuleCode}_is_outsource";
        if (!in_array("{$getModuleCode}.{$outsourceStatusField}", $joinFields)) {
            $joinFields[] = "{$getModuleCode}.{$outsourceStatusField}";
        }


        $joinFields = build_complex_query_fields($joinFields, $keySep);
        if ($needShotAssetLevelQuery) {
            foreach ($shotRelateModules as $relateModuleCode) {
                $joinFields[] = $getModuleCode . ".json->>'$.{$getModuleCode}_{$relateModuleCode}' as {$getModuleCode}{$keySep}{$getModuleCode}_{$relateModuleCode}";
            }
        }

        $EntityEpisodeRelationList = "";
        if (!empty($entityEpisodeRelationFilter) || ($episodeType == 'episode_use' && !empty($episodeSql))) {
            $EntityEpisodeRelationList = model(EntityEpisodeRelationModel::class)
                ->where($entityEpisodeRelationFilter);
            if (!empty($episodeSql)) {
                $EntityEpisodeRelationList = $EntityEpisodeRelationList
                    ->where([
                        'project_id' => $projectId,
                        '_string' => $episodeSql,
                    ]);
            }
            $EntityEpisodeRelationList = $EntityEpisodeRelationList->field('entity_id')->group('entity_id')->buildSql();
        }

        if ($needTotal) {
            // 主表与关联表的数据统计与查询
            $totalQuery = $this
                ->entityModel
                ->alias($getModuleCode)
                ->join("project ON project.id = {$getModuleCode}.project_id", "LEFT")
                ->join("module ON module.id = {$getModuleCode}.module_id", "LEFT")
                ->join("step_workflow ON step_workflow.id = {$getModuleCode}.step_workflow_id", "LEFT")
                ->join("`status` ON `status`.id = JSON_UNQUOTE(JSON_EXTRACT(`{$getModuleCode}`.`json`,'$.{$getModuleCode}_status'))", "LEFT")
                ->where($entityFilter);


            if (!empty($EntityEpisodeRelationList)) {
                $totalQuery->join($EntityEpisodeRelationList . " eer on eer.entity_id = {$getModuleCode}.id");
            }

            if ($episodeType == 'episode_make' && !empty($episodeSql)) {
                $totalQuery->where(['_string' => $episodeSql]);
            }

            if (!empty($groupBy)) {
                $totalQuery->group($groupBy);
            }

            $res['total'] = $totalQuery->count();
        }


        $rowsQuery = $this
            ->entityModel
            ->alias($getModuleCode)
            ->join("project ON project.id = {$getModuleCode}.project_id", "LEFT")
            ->join("module ON module.id = {$getModuleCode}.module_id", "LEFT")
            ->join("step_workflow ON step_workflow.id = {$getModuleCode}.step_workflow_id", "LEFT")
            ->join("user ON user.id = {$getModuleCode}.created_by", "LEFT")
            ->join("`status` ON `status`.id = JSON_UNQUOTE(JSON_EXTRACT(`{$getModuleCode}`.`json`,'$.{$getModuleCode}_status'))", "LEFT")
            ->where($entityFilter)
            ->field($joinFields)
            ->page($page[0], $page[1])
            ->order($order);

        if (!empty($groupBy)) {
            $rowsQuery->group($groupBy);
        }

        if (!empty($EntityEpisodeRelationList)) {
            $rowsQuery->join($EntityEpisodeRelationList . " eer on eer.entity_id = {$getModuleCode}.id");
        }

        if ($episodeType == 'episode_make' && !empty($episodeSql)) {
            $rowsQuery->where(['_string' => $episodeSql]);
        }

        $res['rows'] = $rowsQuery->select();

        if (empty($res['rows'])) {
            return $res;
        }

        $statusList = [];
        $demandStatusList = [];
        if ($needStatus) {
            $statusIdList = array_column($res['rows'], 'status__id', 'status__id');
            unset($statusIdList[null], $statusIdList['']);
            $statusFields[] = 'id';
            if (!empty($statusIdList)) {
                $statusList = StatusService::getInstance()->getMapBy(['id' => ['IN', implode(',', $statusIdList)]], $statusFields, 'id');
            }
        }

        if ($needDemandStatus) {
            $demandStatusList = StatusService::getInstance()->getMapBy(['code' => ['IN', 'wait_demand,already_relate']], $statusFields, 'id');
        }

        // 分镜序列查询
        if (!empty($storyboardSequence)) {
            $storyboardSequencePrimaryIdList = array_column($res['rows'], $storyboardSequenceRelationKeyExpend, $primaryKeyExpend);
            $storyboardSequenceMap = [];
            $sequenceIdList = array_values($storyboardSequencePrimaryIdList);

            if (!empty($sequenceIdList)) {
                $storyboardSequenceList = model(EntityModel::class)
                    ->join("`status` on `status`.id = entity.json->>'$.sequence_status'", 'LEFT')
                    ->where(['entity.id' => ['IN', $sequenceIdList]])
                    ->field("entity.id,entity.name,entity.code,entity.initial_episode_code,entity.showings_number,status.name as status_name,status.code as status_code")
                    ->select();
                $storyboardSequenceList = array_column($storyboardSequenceList, null, 'id');
                foreach ($storyboardSequencePrimaryIdList as $primaryId => $sequenceId) {
                    $storyboardSequenceMap[$primaryId] = $storyboardSequenceList[$sequenceId] ?? null;
                }
            }
        }

        //镜头的序列查询
        $sequenceList = [];
        if ($needSequence) {
            $sequenceFields[] = "id";
            $sequenceFields = array_unique($sequenceFields);
            $shotEntityIds = array_column($res['rows'], "{$getModuleCode}__entity_id", "{$getModuleCode}__entity_id");
            $shotEntityIds = array_filter($shotEntityIds, function ($shotEntityId) {
                return !empty($shotEntityId);
            });
            if (!empty($shotEntityIds)) {
                $sequenceList = model(EntityModel::class)->where(['id' => ["IN", $shotEntityIds]])->field($sequenceFields)->select();
                $sequenceList = array_column($sequenceList, null, 'id');
            }
        }

        // 查询任务是否外包,任务是否开始状态 [is_outsourcing,task_is_start]
//        if (!empty($res['rows']) && !empty(array_column($res['rows'], $getModuleCode . $keySep . 'id'))) {
//            $res['rows'] = $this->getOutsourcingTaskStatusList($res['rows'], $getModuleCode, $keySep);
//        }


        // 媒体查询
        if ($needQueryMedia) {
            $mediaPrimaryKeyMap = [];
            $primaryIdList = array_column($res['rows'], $mediaRelationKeyExpend, $primaryKeyExpend);
            $mediaIdList = [];
            foreach ($primaryIdList as $item) {
                if (empty($item)) {
                    continue;
                }
                $tmpMediaIdList = explode(',', $item);
                foreach ($tmpMediaIdList as $mediaId) {
                    $mediaId = intval($mediaId);
                    if ($mediaId > 0) {
                        $mediaIdList[] = $mediaId;
                    }
                }
            }

            if (count($mediaIdList) > 0) {
                $mediaList = model(MediaModel::class)->where(['id' => ['IN', implode(',', $mediaIdList)]])->field($mediaFields)->select();
                // 格式化数据
                $mediaList = array_map(function ($item) {
                    return CommonService::getFormatAttrData(model(MediaModel::class), $item);
                }, $mediaList);
                $mediaList = array_column($mediaList, null, 'id');
                // 组装主表主键与媒体表数据的关联 map
                foreach ($primaryIdList as $primaryModulePrimaryKey => $item) {
                    $mediaPrimaryKeyMap[$primaryModulePrimaryKey] = [];
                    if (empty($item)) {
                        continue;
                    }
                    $tmpMediaIdList = explode(',', $item);
                    foreach ($tmpMediaIdList as $mediaId) {
                        $mediaId = intval($mediaId);
                        if ($mediaId > 0 && isset($mediaList[$mediaId])) {
                            $mediaPrimaryKeyMap[$primaryModulePrimaryKey][] = $mediaList[$mediaId];
                        }
                    }
                }
            }
        }
        // 待办任务查询
        if ($needTask) {
            $taskPrimaryKeyMap = [];
            $primaryIdList = array_column($res['rows'], $getModuleCodeTaskRelationKeyExpend, $primaryKeyExpend);
            $taskIdList = [];
            foreach ($primaryIdList as $item) {
                if (empty($item)) {
                    continue;
                }
                $tmpTaskIdList = explode(',', $item);
                foreach ($tmpTaskIdList as $taskId) {
                    $taskId = intval($taskId);
                    if ($taskId > 0) {
                        $taskIdList[] = $taskId;
                    }
                }
            }

            if (count($taskIdList) > 0) {
                $taskList = model(TaskModel::class)->where(['id' => ['IN', implode(',', $taskIdList)]])->field(transform_custom_fields(explode(',', $taskFields), 'task'))->select();
                // 格式化数据
                $taskList = array_map(function ($item) {
                    return CommonService::getFormatAttrData(model(TaskModel::class), $item);
                }, $taskList);
                $taskList = array_column($taskList, null, 'id');
                // 组装主表主键与媒体表数据的关联 map
                foreach ($primaryIdList as $primaryModulePrimaryKey => $item) {
                    $taskPrimaryKeyMap[$primaryModulePrimaryKey] = [];
                    if (empty($item)) {
                        continue;
                    }
                    $tmpTaskIdList = explode(',', $item);
                    foreach ($tmpTaskIdList as $taskId) {
                        $taskId = intval($taskId);
                        if ($taskId > 0 && isset($taskList[$taskId])) {
                            $taskPrimaryKeyMap[$primaryModulePrimaryKey][] = $taskList[$taskId];
                        }
                    }
                }
            }
        }

        // 镜头关联的资产和关卡查询
        $relateModuleDataMap = [];
        if ($needShotAssetLevelQuery) {
            $taskPrimaryKeyMap = [];
            $relateModulePrimaryIdStrList = [];
            foreach ($shotRelateModules as $relateModuleCode) {
                $relateModulePrimaryIdStrList = array_merge($relateModulePrimaryIdStrList, array_column($res['rows'], $getModuleCode . $keySep . $getModuleCode . '_' . $relateModuleCode, $primaryKeyExpend));
            }
            $relateModuleEntityIdList = [];
            foreach ($relateModulePrimaryIdStrList as $relateModulePrimaryIdStr) {
                if (empty($relateModulePrimaryIdStr)) {
                    continue;
                }
                foreach (explode(',', $relateModulePrimaryIdStr) as $relateModulePrimaryId) {
                    if (intval($relateModulePrimaryId) <= 0) {
                        continue;
                    }
                    $relateModuleEntityIdList[$relateModulePrimaryId] = $relateModulePrimaryId;
                }
            }
            if (!empty($relateModuleEntityIdList)) {
                $relateModuleDataMap = model(EntityModel::class)->alias('sub')
                    ->where(['id' => ['IN', array_values($relateModuleEntityIdList)]])
                    ->field($relatedModuleQueryFields)->select();
                if (isset($param['filter_relate']) && $param['filter_relate']) {
                    $tmpRelateModuleDataList = [];
                    foreach ($relateModuleDataMap as $item) {
                        if (!empty($item['asset_status']) && $item['asset_status'] == 3) {
                            continue;
                        }
                        if (!empty($item['level_status']) && $item['level_status'] == 3) {
                            continue;
                        }
                        $tmpRelateModuleDataList[] = $item;
                    }
                    $relateModuleDataMap = $tmpRelateModuleDataList;
                }
                $relateModuleDataMap = array_column($relateModuleDataMap, null, 'id');
            }
        }

        // 获取关联ids
        $relationIds = [];
        if ($isRelationQuery == "yes") {
            $primaryIds = array_column($res['rows'], $primaryKeyExpend);
            $relationIds = teamones_request('teamones-olap-sr', 'entity/get_multiple_entity_relation_ids', ["entity_ids" => $primaryIds])['data'];
        }

        $taskStartedStatusList = [StatusService::getInstance()->getTaskDefaultStartStatus()['id'], StatusService::getInstance()->getTaskDoneStatus()['id']];
        // 格式化 填充数据
        foreach ($res['rows'] as &$row) {
            $row = separate_complex_query_data($row, $keySep);
            $row['is_outsourcing'] = !empty($row[$getModuleCode][$outsourceStatusField]) && $row[$getModuleCode][$outsourceStatusField] === 'yes';
            $row['task_is_start'] = !empty($row[$getModuleCode][$statusField]) && in_array($row[$getModuleCode][$statusField], $taskStartedStatusList);
            // 如果需要查询媒体 那么拼接媒体数据
            if ($needQueryMedia) {
                $row['media'] = $mediaPrimaryKeyMap[$row[$getModuleCode]['id']] ?? [];
            }
            if ($storyboardSequence === 'yes') {
                $row['storyboard_motion_sequence'] = $storyboardSequenceMap[$row[$getModuleCode]['id']] ?? null;
            }
            if ($needStatus) {
                $row['status'] = $statusList[$row['status']['id']] ?? null;
            }

            if ($needDemandStatus) {
                $row['demand_status'] = $demandStatusList[$row[$getModuleCode][$getModuleCode . '_demand_status']] ?? null;
            }

            if ($needSequence) {
                $row['sequence'] = $sequenceList[$row[$getModuleCode]['entity_id']] ?? null;
            }
            if ($needTask) {
                $row['task'] = $taskPrimaryKeyMap[$row[$getModuleCode]['id']] ?? [];
            }

            if ($isRelationQuery == "yes") {
                $row['relation_ids'] = $relationIds[$row[$getModuleCode]['id']] ?? null;
            }
            if ($needShotAssetLevelQuery) {
                foreach ($shotRelateModules as $relateModuleCode) {
                    foreach (explode(',', $row[$getModuleCode][$getModuleCode . '_' . $relateModuleCode]) as $relateModulePrimaryId) {
                        if (!array_key_exists($relateModulePrimaryId, $relateModuleDataMap)) {
                            continue;
                        }
                        $row[$getModuleCode . '_' . $relateModuleCode][] = $relateModuleDataMap[$relateModulePrimaryId];
                    }
                }
            }
        }

        return $res;
    }

    /**
     * 剧本导入
     * @param $dataList
     * @param string $moduleCode
     * @return array|array[]
     * @throws Exception
     */
    public function scriptImport($dataList, string $moduleCode)
    {
        /**
         * 1. 导入
         * 2. 更新剧本导入状态
         */
        $episodeIdList = array_column($dataList, 'episode_id');
        $episodeIdList = array_unique($episodeIdList);

        $episodeList = $this->entityModel->where(['id' => ['IN', implode(',', $episodeIdList)]])->select();
        $episodeList = array_column($episodeList, 'code', 'id');

        foreach ($dataList as &$item) {
            $item['initial_episode_code'] = $episodeList[$item['episode_id']] ?? null;
            unset($item['is_generate'], $item['is_assignee'], $item['is_generated_follow']);
        }

        $res = $this->import($dataList, $moduleCode);

        $requestData = [];
        foreach ($res as $index => $resItem) {
            $idList = $dataList[$index]['id'] ?? [];
            $episodeIdsList = $dataList[$index]['episode_ids'] ?? [];
            if (count($idList) > 0 && $resItem['success']) {
                // 通知剧本导入成功
                foreach ($idList as $id) {
                    $requestData[] = ['id' => $id, 'is_generate' => 1];
                }
            }
            // 导入成功 保存集与实体的关联关系
            if ($resItem['success']) {
                foreach ($episodeIdsList as $episodeIdsItem) {
                    model(EntityEpisodeRelationModel::class)->addItem([
                        'entity_id' => $resItem['result']['id'],
                        'entity_module_id' => $resItem['result']['module_id'],
                        'project_id' => $dataList[$index]['project_id'],
                        'episode_id' => $episodeIdsItem['episode_id'],
                        'showings_number' => $episodeIdsItem['showings_number'],
                    ]);
                }
            }
        }
        if (count($requestData) > 0) {
            $docPostResult = teamones_request('teamones-script', 'detailed_list/update_detailed_list_status', ['data' => $requestData]);
        }
        return $res;
    }

    /**
     * 更新概念资产关联关卡
     * @param $data
     * @param string $moduleCode
     * @param $beRelatedEntity
     * @return array
     * @throws Throwable
     */
    public function updateDesignRelation($data, string $moduleCode, $beRelatedEntity)
    {
        $needSyncRelateData = [];

        // 查询出关联了当前实体的概念
        $oldRelatedLevelDesignList = $this->entityModel
            ->where([
                'module_id' => Module::$moduleDictData['module_index_by_code'][$moduleCode]['id'],
                'tenant_id' => $beRelatedEntity['tenant_id'],
                'project_id' => $beRelatedEntity['project_id'],
            ])
            ->where("FIND_IN_SET( '{$beRelatedEntity['name']}' , JSON_UNQUOTE( JSON_EXTRACT( json,'$.level_relate_asset' ) ) )")
            ->select();

        $newRelatedLevelDesignList = [];
        $wantRelateLevelDesign = $data['related_level'] ?? [];

        if (count($wantRelateLevelDesign) > 0) {
            $newRelatedLevelDesignList = $this->entityModel
                ->where([
                    'id' => ['IN', implode(',', $wantRelateLevelDesign)],
                ])
                ->select();
        }

        $this->entityModel->startTrans();


        try {
            $levelNameList = [];
            if (count($oldRelatedLevelDesignList) === 0) {
                // 如果关联的关卡为空 那么直接关联新的
                if (count($wantRelateLevelDesign) > 0) {
                    foreach ($newRelatedLevelDesignList as $newRelatedLevelDesign) {
                        $jsonFields = json_decode($newRelatedLevelDesign['json'], true);
                        $relatedList = explode(',', $jsonFields['level_relate_asset'] ?? "");
                        $relatedList[] = $beRelatedEntity['name'];

                        $relatedStr = implode(',', array_unique($relatedList));
                        $this->entityModel->execute("UPDATE entity SET json = json_set(IFNULL(json,'{}'),'$.level_relate_asset','{$relatedStr}') WHERE id = {$newRelatedLevelDesign['id']}");
                        $needSyncRelateData[] = $newRelatedLevelDesign['id'];
                        $levelNameList[] = $newRelatedLevelDesign['name'];
                    }
                }
            } else {
                // 如果本次操作之前关联的关卡不为空 那么需要对比出 需要移除关联和需要添加关联的概念 进行更新
                $newRelatedLevelDesignList = array_column($newRelatedLevelDesignList, null, 'id');
                $oldRelatedLevelDesignList = array_column($oldRelatedLevelDesignList, null, 'id');
                $oldIdList = array_keys($oldRelatedLevelDesignList);
                $newIdList = array_keys($newRelatedLevelDesignList);
                $needRemovedList = array_diff($oldIdList, $newIdList);
                $needAddedList = array_diff($newIdList, $oldIdList);
                $needKeepList = array_intersect($newIdList, $oldIdList);

                // 需要保留的数据
                foreach ($needKeepList as $keepItemId) {
                    if (isset($newRelatedLevelDesignList[$keepItemId])) {
                        $levelNameList[] = $newRelatedLevelDesignList[$keepItemId]['name'];
                    }
                }

                if (count($needAddedList) > 0) {
                    foreach ($needAddedList as $needAddedId) {
                        $entity = $newRelatedLevelDesignList[$needAddedId] ?? null;
                        if (empty($entity)) {
                            continue;
                        }
                        $jsonFields = json_decode($entity['json'], true);
                        $relatedList = explode(',', $jsonFields['level_relate_asset'] ?? "");
                        $relatedList[] = $beRelatedEntity['name'];

                        $relatedStr = implode(',', array_unique($relatedList));
                        $this->entityModel->execute("UPDATE entity SET json = json_set(IFNULL(json,'{}'),'$.level_relate_asset','{$relatedStr}') WHERE id = {$entity['id']}");
                        $needSyncRelateData[] = $entity['id'];
                        $levelNameList[] = $entity['name'];
                    }
                }

                if (count($needRemovedList)) {
                    foreach ($needRemovedList as $needRemovedId) {
                        $entity = $oldRelatedLevelDesignList[$needRemovedId] ?? null;
                        if (empty($entity)) {
                            continue;
                        }
                        $jsonFields = json_decode($entity['json'], true);
                        $relatedList = explode(',', $jsonFields['level_relate_asset'] ?? "");
                        $relatedList = array_filter($relatedList, function ($item) use ($beRelatedEntity) {
                            return $item !== $beRelatedEntity['name'];
                        });
                        $relatedStr = implode(',', array_unique($relatedList));
                        $this->entityModel->execute("UPDATE entity SET json = json_set(IFNULL(json,'{}'),'$.level_relate_asset','{$relatedStr}') WHERE id = {$entity['id']}");
                        $needSyncRelateData[] = $entity['id'];
                    }
                }
            }

            $levelNameStr = implode(',', $levelNameList);
            $this->entityModel->execute("UPDATE entity set json = json_set(IFNULL(json,'{}'),'$.design_relate_level','{$levelNameStr}') where id = {$beRelatedEntity['id']}");
            $this->entityModel->commit();
        } catch (Throwable $e) {
            $this->entityModel->rollback();
            throw $e;
        }

        return $needSyncRelateData;
    }


    /**
     * 根据类型更新概念资产关联关卡
     * @param $data
     * @param string $moduleCode
     * @return array|array[]|null
     * @throws Throwable
     */
    public function byTypeUpdateDesignRelation($data, string $moduleCode)
    {
        // 无论操作上是场景关联关卡，还是关卡关联场景，代码逻辑上永远是关卡去关联场景（场景将关卡信息追加进design_relate_level，关卡将场景信息追加进level_relate_asset）
        $beRelatedEntity = $this->entityModel->find($data['id']);
        $needSyncRelateData = [];
        $entityJson = json_decode($beRelatedEntity['json'], true);
        if (empty($entityJson['design_category'])) {
            return null;
        } else if ($entityJson['design_category'] == '场景') {
            $needSyncRelateData = $this->updateDesignRelation($data, $moduleCode, $beRelatedEntity);
        } else if ($entityJson['design_category'] == '关卡') {
            $needSyncRelateData = [$beRelatedEntity['id']];
            $oldRelatedLevelAssetList = [];
            if (!empty($entityJson['level_relate_asset'])) {
                $oldRelatedLevelAssetList = $this->entityModel
                    ->where([
                        'module_id' => Module::$moduleDictData['module_index_by_code'][$moduleCode]['id'],
                        'tenant_id' => $beRelatedEntity['tenant_id'],
                        'project_id' => $beRelatedEntity['project_id'],
                        'name' => ['IN', array_filter(explode(',', $entityJson['level_relate_asset']))],
                    ])
                    ->select();
            }

            $newRelatedLevelAssetList = [];
            $wantRelateLevelAsset = $data['related_level'] ?? [];

            if (count($wantRelateLevelAsset) > 0) {
                $newRelatedLevelAssetList = $this->entityModel
                    ->where([
                        'id' => ['IN', implode(',', $wantRelateLevelAsset)],
                    ])
                    ->select();
            }

            $this->entityModel->startTrans();
            try {
                $assetNameList = [];
                if (count($oldRelatedLevelAssetList) === 0) {
                    // 如果关联的资产为空 那么直接关联新的
                    if (count($wantRelateLevelAsset) > 0) {
                        foreach ($newRelatedLevelAssetList as $newRelatedLevelDesign) {
                            $jsonFields = json_decode($newRelatedLevelDesign['json'], true);
                            $relatedList = explode(',', $jsonFields['design_relate_level'] ?? "");
                            $relatedList[] = $beRelatedEntity['name'];
                            $relatedStr = implode(',', array_filter(array_unique($relatedList)));
                            $this->entityModel->execute("UPDATE entity SET json = json_set(IFNULL(json,'{}'),'$.design_relate_level','{$relatedStr}') WHERE id = {$newRelatedLevelDesign['id']}");
                            $assetNameList[] = $newRelatedLevelDesign['name'];
                        }
                    }
                } else {
                    $newRelatedLevelAssetList = array_column($newRelatedLevelAssetList, null, 'id');
                    $oldRelatedLevelAssetList = array_column($oldRelatedLevelAssetList, null, 'id');
                    $oldIdList = array_keys($oldRelatedLevelAssetList);
                    $newIdList = array_keys($newRelatedLevelAssetList);
                    $needRemovedList = array_diff($oldIdList, $newIdList);
                    $needAddedList = array_diff($newIdList, $oldIdList);
                    $needKeepList = array_intersect($newIdList, $oldIdList);

                    // 需要保留的数据
                    foreach ($needKeepList as $keepItemId) {
                        $assetNameList[] = $newRelatedLevelAssetList[$keepItemId]['name'];
                    }

                    if (count($needAddedList) > 0) {
                        foreach ($needAddedList as $needAddedId) {
                            $entity = $newRelatedLevelAssetList[$needAddedId] ?? null;
                            if (empty($entity)) {
                                continue;
                            }
                            $jsonFields = json_decode($entity['json'], true);
                            $relatedList = explode(',', $jsonFields['design_relate_level'] ?? "");
                            $relatedList[] = $beRelatedEntity['name'];

                            $relatedStr = implode(',', array_unique($relatedList));
                            $this->entityModel->execute("UPDATE entity SET json = json_set(IFNULL(json,'{}'),'$.design_relate_level','{$relatedStr}') WHERE id = {$entity['id']}");
                            $assetNameList[] = $entity['name'];
                        }
                    }

                    if (count($needRemovedList)) {
                        foreach ($needRemovedList as $needRemovedId) {
                            $entity = $oldRelatedLevelAssetList[$needRemovedId] ?? null;
                            if (empty($entity)) {
                                continue;
                            }
                            $jsonFields = json_decode($entity['json'], true);
                            $relatedList = explode(',', $jsonFields['design_relate_level'] ?? "");
                            $relatedList = array_filter($relatedList, function ($item) use ($beRelatedEntity) {
                                return $item !== $beRelatedEntity['name'];
                            });
                            $relatedStr = implode(',', array_unique($relatedList));
                            $this->entityModel->execute("UPDATE entity SET json = json_set(IFNULL(json,'{}'),'$.design_relate_level','{$relatedStr}') WHERE id = {$entity['id']}");
                        }
                    }
                }

                $levelNameStr = implode(',', $assetNameList);
                $this->entityModel->execute("UPDATE entity set json = json_set(IFNULL(json,'{}'),'$.level_relate_asset','{$levelNameStr}') where id = {$beRelatedEntity['id']}");
                $this->entityModel->commit();
            } catch (Throwable $e) {
                $this->entityModel->rollback();
                throw $e;
            }
        }
        try {
            if ($needSyncRelateData) {
                $chatService = new ChatService();
                $chat = $chatService->createEntityChat($beRelatedEntity);
                if ($chat) {
                    $message['type'] = 'association_entity_task_message';
                    $message = $chatService->getEntityTaskMessageTemplate($message, request()->getUserId(), $beRelatedEntity);
                    $chatService->pushEntityTaskMessage($message, $chat);
                }
            }
        } catch (Throwable $e) {
            trace(format_exception_info($e), 'ERR');
        }
        $AsyncCallService = AsyncCallService::getInstance();
        // 异步同步关联关系 到关卡和资产
        foreach ($needSyncRelateData as $designId) {
            $AsyncCallService->syncRelationDataAsync($designId, $moduleCode);
            $AsyncCallService->syncRelationDataToEntityRelationShipsAsync($designId);
        }

        return ['relation_changed' => $needSyncRelateData];
    }

    /**
     * 填充实体默认属性
     * @param $entities
     * @param $moduleCode
     * @return bool
     * @throws \Exception
     */
    public function fillEntitiesDefaultAttr($entities, $moduleCode)
    {
        $defaultAttrMap = ModuleService::getInstance()->getModuleDefaultAttr($moduleCode);
        foreach ($entities as $entity) {
            $entity['json'] = $this->mergeJsonFieldWithDefaultAttr($entity['json'], $defaultAttrMap);
            $this->entityModel->modifyItem([
                'id' => $entity['id'],
                'json' => $entity['json'],
            ]);
        }
        return true;
    }

    /**
     * 修改并重新生成任务
     * @param $data
     * @param $moduleCode
     * @param $tenantId
     * @return array|bool|mixed|null
     * @throws Exception
     * @throws Throwable
     */
    public function modifyAndRegenerateTask($data, $moduleCode, $tenantId)
    {

        $this->entityModel->startTrans();

        try {
            $commonService = CommonService::instance('entity');
            $commonService->setCurrentModuleCode($moduleCode);
            $res = $commonService->update($data, $moduleCode, false);
            if ($res !== false) {
                $res['generate_res'] = $this->generateTask(['id' => $data['id']], $moduleCode, $tenantId);
            }
            $this->entityModel->commit();
            return $res;
        } catch (Throwable $e) {
            $this->entityModel->rollback();
            throw $e;
        }
    }

    /**
     * 合并json字段和默认属性字段
     * @param $jsonFields
     * @param array $defaultAttrMap
     * @return array
     */
    public function mergeJsonFieldWithDefaultAttr($jsonFields, array $defaultAttrMap)
    {
        if (empty($jsonFields)) {
            return $defaultAttrMap;
        }
        $attributeFields = array_keys($defaultAttrMap);
        $jsonFields = array_filter($jsonFields, function ($value, $key) use ($attributeFields) {
            return !in_array($key, $attributeFields) || !empty($value);
        }, ARRAY_FILTER_USE_BOTH);
        return array_merge($defaultAttrMap, $jsonFields);
    }

    /**
     * 查询任务需求与附件信息
     * @param $entityId
     * @return array
     */
    public function getSourceDesignRequirementsWithMedia($entityId)
    {
        /**
         *
         * 资产和关卡 查询概念为原始实体
         *
         * 序列和镜头 查询分镜为原始实体
         *
         * 序列需要查询所有镜头对应分镜的media
         *
         *
         *
         */
        $entity = $this->entityModel->find($entityId);
        $jsonFields = json_decode($entity['json'], true);
        $entity = array_merge($jsonFields, $entity);
        $taskModule = Module::$moduleDictData['module_index_by_id'][$entity['module_id']];

        $res = [
            'media_list' => [],
            'entity' => $entity,
        ];
        $mediaIdList = [];
        switch ($taskModule['code']) {
            case 'sequence':
                // 查询关联的镜头
                $shotList = $this->entityModel
                    ->where([
                        'tenant_id' => $entity['tenant_id'],
                        'entity_id' => $entity['id'],
                    ])
                    ->field("JSON_UNQUOTE(JSON_EXTRACT(json,'$.shot_storyboard')) as storyboard_id")
                    ->select();
                if (count($shotList)) {
                    // 查询关联的分镜
                    $storyboardList = $this->entityModel
                        ->where([
                            'id' => ['IN', implode(',', array_column($shotList, 'storyboard_id'))],
                        ])
                        ->field("JSON_UNQUOTE(JSON_EXTRACT(json,'$.storyboard_media')) as media_id_str")
                        ->select();
                    if (count($storyboardList)) {
                        // 媒体id合并
                        $mediaIdList = [];
                        foreach ($storyboardList as $storyboard) {
                            if (!empty($storyboard['media_id_str'])) {
                                $mediaIdList = array_merge($mediaIdList, explode(',', $storyboard['media_id_str']));
                            }
                        }
                    }
                }
                break;
            case 'asset':
            case 'level':
                if (!empty($entity[$taskModule['code'] . '_design'])) {
                    // 查询概念
                    $design = $this->entityModel->field("id,name,description,character_seal,JSON_UNQUOTE(JSON_EXTRACT(json,'$.design_media')) as media_id_str")->find($entity[$taskModule['code'] . '_design']);
                    $res['entity'] = $design;
                    if (!empty($design['media_id_str'])) {
                        $mediaIdList = explode(',', $design['media_id_str']);
                    } else {
                        goto QUERY_CURRENT_MEDIA;
                    }
                } else {
                    // 概念不存在 检查自己的媒体信息
                    goto QUERY_CURRENT_MEDIA;
                }

                break;
            case 'storyboard':
            case 'design':
            case 'shot':
            default:
                QUERY_CURRENT_MEDIA:
                if (!empty($entity[$taskModule['code'] . '_media'])) {
                    $mediaIdList = explode(',', $entity[$taskModule['code'] . '_media']);
                }
        }

        if (count($mediaIdList) > 0) {
            $mediaService = MediaService::getInstance();
            $mediaList = $mediaService->selectMedia(['id' => ['IN', implode(',', $mediaIdList)]], 'id,thumb,type,param');
            foreach ($mediaList as &$mediaItem) {
                $mediaItem = CommonService::getFormatAttrData(model(MediaModel::class), $mediaItem);
            }
            $res['media_list'] = $mediaList;
        }
        return $res;
    }

    /**
     * 分镜序列列表
     * @param $param
     * @param string $getModuleCode
     * @return array
     */
    public function getStoryboardSequenceTree($param, string $getModuleCode)
    {
        /**
         * 先查询出序列信息
         * 在组装新的filter 筛选出所有的分镜
         * 组装数据
         *      sequence => [storyboard]
         */
        $entityFilter = parse_filter_to_tp_where($param['filter'], $getModuleCode);
        $page = $param['page'] ?? [1, 20];
        $sequenceFieldName = "JSON_UNQUOTE(JSON_EXTRACT($getModuleCode.json,'$.{$getModuleCode}_sequence'))";
        $orderBy = $param['order'] ?? ["$getModuleCode.project_id", "$getModuleCode.initial_episode_code", $sequenceFieldName];
        $orderBy = transform_normal_order($orderBy, $getModuleCode);

        $res['total'] = $this->entityModel->query('select count(*) as total from (' . $this->entityModel->alias($getModuleCode)
                ->join("`status` ON `status`.id = JSON_UNQUOTE(JSON_EXTRACT(`{$getModuleCode}`.`json`,'$.{$getModuleCode}_status'))", "LEFT")
                ->where($entityFilter)
                ->field("$getModuleCode.project_id,$getModuleCode.initial_episode_code,$sequenceFieldName as sequence_name")
                ->group("$getModuleCode.project_id,$getModuleCode.initial_episode_code,$sequenceFieldName")->buildSql() . ' ) as count_sub_query')[0]['total'];

        $sequenceList = $this->entityModel->alias($getModuleCode)
            ->join("`status` ON `status`.id = JSON_UNQUOTE(JSON_EXTRACT(`{$getModuleCode}`.`json`,'$.{$getModuleCode}_status'))", "LEFT")
            ->where($entityFilter)
            ->page($page[0], $page[1])
            ->field("$getModuleCode.project_id,$getModuleCode.initial_episode_code,$sequenceFieldName as sequence_name")
            ->group("$getModuleCode.project_id,$getModuleCode.initial_episode_code,$sequenceFieldName")
            ->order($orderBy)
            ->select();
        if (empty($sequenceList)) {
            return [];
        }


        $param['page'] = [1, C("database.database_max_select_rows")];
        $storyboardList = $this->getList($param, $getModuleCode);

        if ($storyboardList['total'] <= 0) {
            return [];
        }
        $storyboardList = $storyboardList['rows'];
        $storyboardListGroupedBySequence = [];
        // 分组数据 sequence=>[storyboard]

        foreach ($storyboardList as $storyboard) {
            if (!empty($storyboard[$getModuleCode]['initial_episode_code'])) {
                $storyboardListGroupedBySequence[$storyboard[$getModuleCode]['initial_episode_code']][$storyboard[$getModuleCode]['storyboard_sequence']][] = $storyboard;
            }
        }
        unset($storyboardList);
        $res['rows'] = [];

        $sequenceData = [];
        $storyboardData = [];
//        return $storyboardListGroupedBySequence;
        foreach ($storyboardListGroupedBySequence as $episodeName => $storyboardListGroupedBySequenceItem) {
            foreach ($storyboardListGroupedBySequenceItem as $sequenceName => $storyboardList) {
                if ($sequenceName === "") {
                    foreach ($storyboardList as $storyboardItem) {
                        if (isset($storyboardItem['media'])) {
                            $storyboardItem['storyboard']['media'] = $storyboardItem['media'];
                        }
                        $storyboardItem = $storyboardItem['storyboard'];
                        $storyboardData[] = $storyboardItem;
                    }
                } else {
                    $key = md5("{$episodeName}_{$sequenceName}");
                    $storyboardsIntersection = [];
                    foreach ($storyboardList as $index => $storyboardItem) {
                        $storyboardsIntersection[] = $storyboardItem['storyboard'];
                        if (isset($storyboardItem['media'])) {
                            $storyboardItem['storyboard']['media'] = $storyboardItem['media'];
                        }

                        $storyboardList[$index] = $storyboardItem['storyboard'];
                    } //筛选出共同条件

                    $currentItem = $this->arrayIntersectAssoc($storyboardsIntersection);
                    unset($currentItem['name'], $currentItem['code']);
                    $sequenceData[$key] = $currentItem;
                    $sequenceData[$key]['name'] = $sequenceName;
                    $sequenceData[$key]['initial_episode_code'] = $episodeName;
                    $sequenceData[$key]['children'] = $storyboardList;
                }
            }
        }

        $sequenceData = array_values($sequenceData);
        $res['rows'] = array_merge($sequenceData, $storyboardData);

        return $res;
    }

    public function arrayIntersectAssoc($array)
    {
        $currentItem = $array[0];
        foreach ($array as $key => $item) {
            if ($key > 0) {
                if (!empty($currentItem)) {
                    $unsetKey = [];
                    foreach ($currentItem as $currentItemKey => $currentItemValue) {
                        if (!isset($item[$currentItemKey])) { //如果不存在当前分类不存在，则删除
                            $unsetKey[] = $currentItemKey;
                        } else {
                            if (gettype($currentItemValue) == gettype($item[$currentItemKey])) {


                                if (is_string($currentItemValue) && (strpos($currentItemValue, ',') !== false)) {


                                    $currentItemValueList = explode(',', $currentItemValue);
                                    $itemValueList = explode(',', $item[$currentItemKey]);

                                    $currentItemValueList = array_unique($currentItemValueList);
                                    $itemValueList = array_unique($itemValueList);

                                    $currentItem[$currentItemKey] = array_merge($currentItemValueList, $itemValueList);
                                    $currentItem[$currentItemKey] = join(',', $currentItem[$currentItemKey]);
                                } elseif ($currentItemValue !== $item[$currentItemKey]) { //如果值不相同，则删除
                                    $unsetKey[] = $currentItemKey;
                                }
                            } else {
                                $unsetKey[] = $currentItemKey;
                            }
                        }
                    }
                    foreach ($unsetKey as $key) {
                        unset($currentItem[$key]);
                    }
                } else {
                    return $currentItem;
                }
            }
        }
        return $currentItem;
    }

    /**
     * 分镜序列列表
     * @param $param
     * @param int $getTenantId
     * @param string $getModuleCode
     * @return array
     */
    public function getStoryboardSequenceList($param, int $getTenantId, string $getModuleCode)
    {
        $res = [
            'total' => 0,
            'rows' => [],
        ];
        /**
         * 先查询出序列信息
         * 在组装新的filter 筛选出所有的分镜
         * 组装数据
         *      sequence => [storyboard]
         */
        $entityFilter = parse_filter_to_tp_where($param['filter'], $getModuleCode);
        $entityFilter[$getModuleCode . '.tenant_id'] = $getTenantId;
        $entityFilter[$getModuleCode . '.module_id'] = module_by_code($getModuleCode)['id'];

        $sequenceStatusList = StatusService::getInstance()->entityStatusList('storyboard', 'sequence');
        $statusCodeList = [];
        foreach ($sequenceStatusList as $sequenceStatus) {
            foreach ($sequenceStatus['code_list'] as $statusCode) {
                $statusCodeList[] = $statusCode;
            }
        }
        if (!empty($statusCodeList)) {
            $entityFilter[] = ['status.code' => ['IN', implode(',', $statusCodeList)]];
        }

        $page = $param['page'] ?? [1, 20];
        $sequenceFieldName = "JSON_UNQUOTE(JSON_EXTRACT($getModuleCode.json,'$.{$getModuleCode}_sequence'))";
        $orderBy = $param['order'] ?? ["$getModuleCode.project_id", "$getModuleCode.initial_episode_code", "$getModuleCode.showings_number", $sequenceFieldName];
        $orderBy = transform_normal_order($orderBy, $getModuleCode);
        $res['total'] = $this->entityModel->query('select count(*) as total from ' . $this->entityModel->alias($getModuleCode)
                ->join("`status` ON `status`.id = JSON_UNQUOTE(JSON_EXTRACT(`{$getModuleCode}`.`json`,'$.{$getModuleCode}_status'))", "LEFT")
                ->where($entityFilter)
//                ->where("$sequenceFieldName IS NOT NULL AND $sequenceFieldName <> ''")
                ->field("$getModuleCode.project_id,$getModuleCode.initial_episode_code,$sequenceFieldName as sequence_name")
                ->group("$getModuleCode.project_id,$getModuleCode.initial_episode_code,$sequenceFieldName")->buildSql() . ' as count_sub_query')[0]['total'];

        $sequenceList = $this->entityModel->alias($getModuleCode)
            ->join("`status` ON `status`.id = JSON_UNQUOTE(JSON_EXTRACT(`{$getModuleCode}`.`json`,'$.{$getModuleCode}_status'))", "LEFT")
            ->where($entityFilter)
//            ->where("$sequenceFieldName IS NOT NULL AND $sequenceFieldName <> ''")
            ->page($page[0], $page[1])
            ->field("$getModuleCode.project_id,$getModuleCode.initial_episode_code,$sequenceFieldName as sequence_name")
            ->group("$getModuleCode.project_id,$getModuleCode.initial_episode_code,$sequenceFieldName")
            ->order($orderBy)
            ->select();
        if (empty($sequenceList)) {
            return $res;
        }
        $param['filter'][] = [
            "$getModuleCode.{$getModuleCode}_sequence" => ['IN', implode(',', array_column($sequenceList, 'sequence_name'))],
        ];
        $param['page'] = [1, C("database.database_max_select_rows")];
        $storyboardList = $this->getList($param, $getModuleCode, false);
        if (empty($storyboardList['rows'])) {
            return $res;
        }
        $storyboardList = $storyboardList['rows'];
        $storyboardListGroupedBySequence = [];
        // 分组数据 sequence=>[storyboard]
        foreach ($storyboardList as $storyboard) {
            $storyboardListGroupedBySequence[$storyboard[$getModuleCode]['initial_episode_code']][$storyboard[$getModuleCode]['storyboard_sequence']][] = $storyboard;
        }
        unset($storyboardList);
        $res['rows'] = [];
        $realSequenceFilter = [];
        foreach ($sequenceList as $sequence) {
            $storyboardList = $storyboardListGroupedBySequence[$sequence['initial_episode_code']][$sequence['sequence_name']] ?? [];
            // 跳过 空sequence
            if (empty($storyboardList)) {
                continue;
            }
            $status = null;
            $mediaList = [];
            $taskIsStart = false;
            $isOutsourcing = false;

            foreach ($storyboardList as $storyboard) {
                // 状态合并,媒体合并
                if ($status === null && !empty($storyboard['status'])) {
                    $status = $storyboard['status'];
                }
                if (!empty($storyboard['status']) && $storyboard['status']['code'] === 'wait_for_generated' && (empty($status) || $status['code'] !== 'wait_for_generated')) {
                    $status = $storyboard['status'];
                }
                if (isset($storyboard['media']) && count($storyboard['media']) > 0) {
                    $mediaList = array_merge($mediaList, $storyboard['media']);
                }
                if ($storyboard['task_is_start']) {
                    $taskIsStart = true;
                }
                if ($storyboard['is_outsourcing']) {
                    $isOutsourcing = true;
                }
            }
            $tmpSequence = $storyboardList[0][$getModuleCode];
            $tmpSequence[$getModuleCode . '_list'] = $storyboardList;
            $tmpSequence['status'] = $status;
            $tmpSequence['name'] = $sequence['sequence_name'];
            $tmpSequence['media'] = $mediaList;
            $tmpSequence['task_is_start'] = $taskIsStart;
            $tmpSequence['is_outsourcing'] = $isOutsourcing;

            $res['rows'][] = $tmpSequence;
            if (!$taskIsStart || !$isOutsourcing) {
                $realSequenceFilter[] = [
                    'project_id' => $tmpSequence['project_id'],
                    'initial_episode_code' => $tmpSequence['initial_episode_code'],
                    'showings_number' => $tmpSequence['showings_number'],
                    'name' => $tmpSequence['name'],
                ];
            }

        }

        // 查询序列任务是否开始 是否外包
        if (!empty($realSequenceFilter)) {
            $realSequenceFilter = [
                [
                    ...$realSequenceFilter,
                    '_logic' => 'or',
                ],
                'module_id' => module_by_code('sequence')['id'],
            ];
            $taskStartedStatusList = [StatusService::getInstance()->getTaskDefaultStartStatus()['id'], StatusService::getInstance()->getTaskDoneStatus()['id']];
            $realSequenceList = model(EntityModel::class)
                ->where($realSequenceFilter)
                ->field("id,project_id,initial_episode_code,showings_number,name,json->>'$.sequence_status' as sequence_status,json->>'$.sequence_is_outsource' as is_outsource")
                ->select();
            $sequenceMarkMap = [];
            foreach ($realSequenceList as $realSequence) {
                $sequenceKey = sprintf("%s-%s-%s-%s", $realSequence['project_id'], $realSequence['initial_episode_code'], $realSequence['showings_number'], $realSequence['name']);
                $sequenceMarkMap[$sequenceKey] = [
                    'task_is_start' => in_array($realSequence['sequence_status'], $taskStartedStatusList),
                    'is_outsourcing' => $realSequence['is_outsource'] === 'yes',
                ];
            }

            foreach ($res['rows'] as &$row) {
                if ($row['task_is_start'] && $row['is_outsourcing']) {
                    continue;
                }
                $sequenceKey = sprintf("%s-%s-%s-%s", $row['project_id'], $row['initial_episode_code'], $row['showings_number'], $row['name']);
                if (!isset($sequenceMarkMap[$sequenceKey])) {
                    continue;
                }
                if ($sequenceMarkMap[$sequenceKey]['task_is_start']) {
                    $row['task_is_start'] = true;
                }
                if ($sequenceMarkMap[$sequenceKey]['is_outsourcing']) {
                    $row['is_outsourcing'] = true;
                }
            }
        }
        // 处理任务是否开始,是否外包
//        $res['rows'] = $this->getSeqTaskList($res['rows']);
        return $res;
    }

    /**
     * 修改并重新生成任务
     * @param $data
     * @return array
     * @throws Exception
     * @throws Throwable
     */
    public function modifySequenceTask($data)
    {
        $sequenceCode = 'sequence';
        $shotModuleCode = 'shot';

        $commonService = CommonService::instance('entity');
        $commonService->setCurrentModuleCode($sequenceCode);
        $sequenceData = $data['sequence_data'];

        $this->entityModel->startTrans();
        try {
            $sequenceSave = $commonService->update($sequenceData, $sequenceCode, false);
            $sequenceEntity = $this->entityModel->where(['id' => $sequenceData['id']])->find();
            $tenantId = $sequenceEntity['tenant_id'];

            $res = [];
            if ($sequenceSave !== false) {
                $res['sequence_generate_res'] = $this->generateTask(['id' => $sequenceData['id']], $sequenceCode, $tenantId);
            }

            //批量更新镜头
            if (!empty($data['shot_data'])) {
                $shotEntity = $this->entityModel->where(['entity_id' => $sequenceData['id']])->select();
                $commonService->setCurrentModuleCode($shotModuleCode);
                foreach ($shotEntity as $shotEntityItem) {
                    $shotData = $data['shot_data'];
                    $shotData['id'] = $shotEntityItem['id'];
                    $shotSave = $commonService->update($shotData, $shotModuleCode, false);
                    if ($shotSave !== false) {
                        $res['shot_generate_res'] = $this->generateTask(['id' => $shotEntityItem['id']], $shotModuleCode, $tenantId);
                    }
                }
            }

            $this->entityModel->commit();
            return $res;
        } catch (Throwable $e) {
            $this->entityModel->rollback();
            throw $e;
        }
    }

    /**
     * 更新镜头任务
     * @param $data
     * @return array
     * @throws Exception
     * @throws Throwable
     */
    public function modifyShotEntityStep($data)
    {
        $res = [];
        $shotModuleCode = 'shot';
        $commonService = CommonService::instance('entity');
        $commonService->setCurrentModuleCode("shot");
        $shotEntity = $this->entityModel->find($data['id']);
        if (empty($shotEntity)) {
            return $res;
        }
        $shotSave = $commonService->update($data, $shotModuleCode, false);
        if ($shotSave !== false) {
            // 分镜同步修改
            $storyboardFields = Module::$moduleDictData['field_index_by_code']['storyboard'];
            $storyboardFixFields = array_keys($storyboardFields['fixed'] ?? []);
            $storyboardCustomFields = array_keys($storyboardFields['custom'] ?? []);
            $storyboardUpdateData = [];
            foreach ($data as $k => $v) {
                if ($k == 'id') {
                    continue;
                }
                if (in_array($k, $storyboardFixFields)) {
                    $storyboardUpdateData[$k] = $v;
                    continue;
                }
                $k = str_replace_once('shot', 'storyboard', $k);
                if (in_array($k, $storyboardCustomFields)) {
                    $storyboardUpdateData[$k] = $v;
                }
            }
            if (!empty($storyboardUpdateData) && !empty($shotSave['shot_storyboard'])) {
                $storyboardUpdateData['id'] = $shotSave['shot_storyboard'];
                $commonService
                    ->setCurrentModuleCode('storyboard')
                    ->update($storyboardUpdateData, 'storyboard', false);
            }
            $res['shot_generate_res'] = $this->generateTask(['id' => $shotEntity['id']], $shotModuleCode, $shotEntity['tenant_id']);
        }
        return $res;
    }

    /**
     * 获取序列条件
     * @param $filter
     * @return array
     */
    public function getSequenceFilter($filter)
    {
        $Id = $filter['id'];
        $sequence = model(EntityModel::class)
            ->join("module on entity.module_id = module.id")
            ->where(["entity.id" => $Id])
            ->field("
            JSON_UNQUOTE(JSON_EXTRACT(entity.json,CONCAT('$.',module.`code`,'_production_method') )) AS production_method,
            JSON_UNQUOTE(JSON_EXTRACT(entity.json,CONCAT('$.',module.`code`,'_cloth_solution') )) AS cloth_solution,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.shot_fx')) AS shot_fx,
            module.code as module_code 
            ")
            ->find();

        $shot = model(EntityModel::class)->where(['entity_id' => $Id])
            ->field("JSON_UNQUOTE(JSON_EXTRACT(json, '$.shot_fx')) AS shot_fx")
            ->find();
        if (!empty($shot['shot_fx'])) {
            $sequence['shot_fx'] = $shot['shot_fx'];
        }


        $res['sequence_production_method'] = $sequence['production_method'] ?? null;
        $res['sequence_cloth_solution'] = $sequence['cloth_solution'] ?? null;
        $res['shot_fx'] = $sequence['shot_fx'] ?? null;

        return $res;
    }

    /**
     * 计算实体工时
     * @param $entityId
     * @return bool|int|string
     * @throws \Exception
     */
    public function calculateEntityManhour($entityId)
    {
        $entityData = $this->entityModel->find($entityId);
        if (empty($entityData)) {
            return false;
        }
        $moduleId = $entityData['module_id'];
        $stepWorkflowId = $entityData['step_workflow_id'] ?? 0;
        if ((int)$stepWorkflowId <= 0) {
            return false;
        }
        $stepCategoryDict = StepWorkflowService::getInstance()->getStepCategoryDictByStepWorkflowId([$stepWorkflowId]);
        if (empty($stepCategoryDict)) {
            return false;
        }
        $totalManHour = 0;
        $stepSkuService = StepSkuService::getInstance();
        foreach ($stepCategoryDict[$stepWorkflowId]['step_category_data'] as $stepCategoryItem) {
            list($tmpHour,) = $stepSkuService->autoCalculationManHourByEntity($entityId, $moduleId, 'step_category', $stepCategoryItem['id'], $stepWorkflowId);
            $totalManHour += $tmpHour;
        }
        $modifyData = [
            'id' => $entityId,
            'estimate_hours' => $totalManHour,
        ];
        //如果没有设置成本工时  或者成本工时小于等于0 则等于预估工时
        if (!isset($entityData['cost_hours']) || (int)$entityData['cost_hours'] <= 0) {
            $modifyData['cost_hours'] = $totalManHour;
        }
        return $this->entityModel->save($modifyData);
    }

    /**
     * 获取实体SKU值
     * @param $entityId
     * @param $attributeRule
     * @return array
     */
    public function getEntitySkuValues($entityId, $attributeRule)
    {
        $fields = [];
        foreach ($attributeRule as $attribute) {
            $fields[] = "JSON_UNQUOTE(JSON_EXTRACT(json, '$.{$attribute['field']}')) AS {$attribute['field']}";
        }
        $entityData = $this->entityModel->field(join(',', $fields))
            ->where(['id' => $entityId])
            ->find();
        $entitySkuVal = [];
        foreach ($attributeRule as $attribute) {
            APP_DEBUG && trace("{$attribute['field']}:{$entityData[$attribute['field']]}", 'DEBUG');
            $entitySkuVal[] = $entityData[$attribute['field']];
        }
        return $entitySkuVal;
    }

    /**
     * 通过存在的数据值获取实体SKU值
     * @param $entityAttrData
     * @param $attributeRule
     * @return array
     */
    public function getEntitySkuValuesByData($entityAttrData, $attributeRule)
    {
        $entitySkuVal = [];
        foreach ($attributeRule as $attribute) {
            $entitySkuVal[] = $entityAttrData[$attribute['field']];
        }

        return $entitySkuVal;
    }

    /**
     * 获取任务工序SKU规则映射
     * @param $entityId
     * @param $stepData
     * @param $attributeRule
     * @return array
     */
    public function getTaskStepManHour($entityId, $stepData, $attributeRule)
    {
        $entitySkuVal = $this->getEntitySkuValues($entityId, $attributeRule);
        array_unshift($entitySkuVal, $stepData['name']);
        $entitySkuStrMd5 = md5(join(':', $entitySkuVal));
        $stepSkuModelModel = model(StepSkuModel::class);
        $manHour = $stepSkuModelModel->where(['attribute_value_md5' => $entitySkuStrMd5])->field("id,man_hour")->find();
        APP_DEBUG && trace(var_export(join(':', $entitySkuVal), true), "DEBUG");
        APP_DEBUG && trace(var_export($entitySkuStrMd5, true), "DEBUG");
        APP_DEBUG && trace(var_export($manHour, true), "DEBUG");
        if (empty($manHour)) {
            return [0, 0];
        }
        return [$manHour['man_hour'], $manHour['id']];
    }

    /**
     * 批量更新数据
     * @param $dataList
     * @param string $getModuleCode
     * @return array
     * @throws \Exception
     */
    public function batchUpdate($dataList, string $getModuleCode)
    {
        $commonService = CommonService::instance('entity');
        $res = [];
        $asyncCallService = AsyncCallService::getInstance();
        foreach ($dataList as $data) {
            $this->beforeUpdate($data, $getModuleCode);
            $res[] = $commonService->update($data, $getModuleCode, false);
            $asyncCallService->syncRelationDataAsync($data['id'], $getModuleCode);
            $asyncCallService->syncRelationDataToEntityRelationShipsAsync($data['id']);
        }
        return $res;
    }

    /**
     * 批量导入剧本的场次
     * @param $data
     * @return array|mixed
     */
    public function fillBackScriptSceneData($data)
    {
        $sceneData = teamones_request('teamones-script', 'entity_list_detailed_list/batch_import_scene', ["data" => $data]);
        return $sceneData['data'] ?? [];
    }

    /**
     * 同步剧本的场次
     * @return array|mixed|null
     */
    public function syncSceneToScript()
    {

        $entityList = model(EntityModel::class)->field('project_id,initial_episode_code,showings_number')
            ->group('project_id,initial_episode_code,showings_number')
            ->select();

        //去重
        $sceneData = [];
        $episodeCodeGroupedByProject = [];
        foreach ($entityList as &$entityListItem) {
            if (empty($entityListItem['initial_episode_code']) || empty($entityListItem['showings_number']) || strpos($entityListItem['showings_number'], "'")) {
                continue;
            }
            $sceneData[] = $entityListItem;

            if (!isset($episodeCodeGroupedByProject[$entityListItem['project_id']])) {
                $episodeCodeGroupedByProject[$entityListItem['project_id']] = [$entityListItem['initial_episode_code']];
            } elseif (!in_array($entityListItem['initial_episode_code'], $episodeCodeGroupedByProject[$entityListItem['project_id']])) {
                $episodeCodeGroupedByProject[$entityListItem['project_id']][] = $entityListItem['initial_episode_code'];
            }
        }

        // 获取项目与集的映射
        $episodeMap = EpisodeService::getInstance()->getMultipleProjectEpisodes($episodeCodeGroupedByProject);

        foreach ($sceneData as $sceneKey => $sceneDataItem) {
            if (isset($episodeMap[$sceneDataItem['project_id']][$sceneDataItem['initial_episode_code']])) {
                $sceneData[$sceneKey]['episode_id'] = $episodeMap[$sceneDataItem['project_id']][$sceneDataItem['initial_episode_code']]['id'];
                unset($sceneData[$sceneKey]['initial_episode_code']);
            } else {
                unset($sceneData[$sceneKey]);
            }
        }

        if (!empty($sceneData)) {
            $sceneData = array_values($sceneData);
            return $this->fillBackScriptSceneData($sceneData);
        }

        return null;

    }

    /**
     * 批量导入剧本的清单
     * @param $data
     * @return array|mixed
     */
    public function fillBackScriptDetailedListData($data)
    {
        $sceneData = teamones_request('teamones-script', 'entity_list_detailed_list/batch_import_detailed_list', ["data" => $data]);
        return $sceneData['data'] ?? [];
    }

    /**
     * 同步清单到剧本拆分中
     * @return array|mixed|null
     */
    public function syncListToScript()
    {
        $filter = [
            'module_id' => Module::$moduleDictData['module_index_by_code']['design']['id'],
        ];

        if (!empty($episodeCode)) {
            $filter['initial_episode_code'] = $episodeCode;
        }

        $entityField = 'name,project_id,initial_episode_code,showings_number,JSON_UNQUOTE(JSON_EXTRACT(json, "$.design_category")) AS design_category,
        JSON_UNQUOTE(JSON_EXTRACT(json, "$.design_perform_species")) AS design_perform_species';

        $entityList = model(EntityModel::class)
            ->where($filter)
            ->field($entityField)
            ->select();

        //没有场次的不同步
        $listData = [];
        $episodeCodeGroupedByProject = [];
        foreach ($entityList as &$entityListItem) {
            if (empty($entityListItem['initial_episode_code']) || empty($entityListItem['showings_number']) || strpos($entityListItem['showings_number'], "'")) {
                continue;
            }

            $listData[] = $entityListItem;
            if (!array_key_exists($entityListItem['project_id'], $episodeCodeGroupedByProject)) {
                $episodeCodeGroupedByProject[$entityListItem['project_id']] = [$entityListItem['initial_episode_code']];
            } else {
                $episodeCodeGroupedByProject[$entityListItem['project_id']][] = $entityListItem['initial_episode_code'];
            }
        }

        //条件组装
        $episodeMap = EpisodeService::getInstance()->getMultipleProjectEpisodes($episodeCodeGroupedByProject);

        foreach ($listData as $sceneKey => $listDataItem) {
            if (isset($episodeMap[$listDataItem['project_id']][$listDataItem['initial_episode_code']])) {
                $listData[$sceneKey]['episode_id'] = $episodeMap[$listDataItem['project_id']][$listDataItem['initial_episode_code']]['id'];
                unset($listData[$sceneKey]['initial_episode_code']);
            } else {
                unset($listData[$sceneKey]);
            }
        }

        if (!empty($listData)) {
            $listData = array_values($listData);
            return $this->fillBackScriptDetailedListData($listData);
        }

        return null;
    }

    /**
     * 维护概念数据到剧本
     * @param $param
     * @param $code
     * @return array|false|mixed|string
     * @throws \Exception
     */
    public function maintainDesignDataToScript($param, $code)
    {
        $data = [];
        if ($code === "design") {
            $entityFilter = [
                "name" => $param['data']['name'],
                "project_id" => $param['data']['project_id'],
                "module_id" => Module::$moduleDictData['module_index_by_code']['design']['id'],
            ];

            //相同概念 ,不同集场的需要维护集场关系
            $designEntity = $this->entityModel->where($entityFilter)->find();
            if (!empty($designEntity)) {
                $data = [
                    'old' => $designEntity,
                    'new' => $param['data'],
                ];
                $this->designMultiEpisodeCreateCheck($data);
                return $designEntity;
            }
        }
        return $data;
    }

    /**
     * 同步概念集关系到资产
     * @param $param
     * @return int
     * @throws \Exception
     */
    public function syncEpisodeRelationToAsset($param)
    {
        $isAsync = $param['is_async'] ?? 'yes';
        if ($isAsync) {
            Client::send('sync_design_episode_relation_to_asset', build_queue_data_with_xu(['filter' => $param['filter']]));
            $res = 0;
        } else {
            $res = $this->designEpisodeRelationToAsset($param['filter']);
        }
        return $res;
    }

    /**
     * 同步概念集关系到资产
     * @param $filter
     * @return int
     * @throws \Exception
     */
    public function designEpisodeRelationToAsset($filter)
    {
        $changed = 0;
        $projectId = $filter['project_id'];
        $filter['module_id'] = Module::$moduleDictData['module_index_by_code']['design']['id'];
        $designList = $this->entityModel->where($filter)->field('id')->select();
        $designIdList = array_column($designList, 'id');
        $assetModuleId = Module::$moduleDictData['module_index_by_code']['asset']['id'];
        $assetListFilter = [
            'project_id' => $projectId,
            'module_id' => $assetModuleId,
            "JSON_UNQUOTE(JSON_EXTRACT(json,'$.asset_design'))" => ['IN', $designIdList],
        ];
        $assetList = $this->entityModel->where($assetListFilter)->field("id,JSON_UNQUOTE(JSON_EXTRACT(json,'$.asset_design')) as design_id")->select();
        $assetIdList = array_column($assetList, 'id');
        $assetDesignMap = array_column($assetList, 'id', 'design_id');
        if (count($assetIdList) === 0) {
            return $changed;
        }

        $designEpisodeRelation = model(EntityEpisodeRelationModel::class)->where(['entity_id' => ['IN', $designIdList]])->select();
        $designEpisodeRelation = array_group_by($designEpisodeRelation, 'entity_id');
        $assetEpisodeRelation = model(EntityEpisodeRelationModel::class)->where(['entity_id' => ['IN', $assetIdList]])->select();
        $assetEpisodeRelation = array_group_by($assetEpisodeRelation, 'entity_id');
        foreach ($assetDesignMap as $designId => $assetId) {
            $currentDesignRelation = $designEpisodeRelation[$designId] ?? [];
            $currentAssetRelation = $assetEpisodeRelation[$assetId] ?? [];
            if (count($currentDesignRelation) > 0) {
                $currentDesignRelation = array_group_by($currentDesignRelation, 'episode_id');
                $currentAssetRelation = array_group_by($currentAssetRelation, 'episode_id');
                foreach ($currentDesignRelation as $episodeId => $designShowingsNumberList) {
                    $designShowingsNumberList = array_column($designShowingsNumberList, 'showings_number', 'showings_number');
                    $assetShowingsNumberList = $currentAssetRelation[$episodeId] ?? [];
                    $assetShowingsNumberList = array_column($assetShowingsNumberList, 'showings_number', 'showings_number');
                    $needAdded = array_diff($designShowingsNumberList, $assetShowingsNumberList);
                    $needDeleted = array_diff($assetShowingsNumberList, $designShowingsNumberList);
                    $addList = [];
                    foreach ($needAdded as $showingsNumber) {
                        $addList[] = [
                            'entity_id' => $assetId,
                            'entity_module_id' => $assetModuleId,
                            'episode_id' => $episodeId,
                            'created' => time(),
                            'project_id' => $projectId,
                            'showings_number' => $showingsNumber,
                        ];
                    }
                    if (count($addList) > 0) {
                        model(EntityEpisodeRelationModel::class)->addAll($addList);
                        $changed += count($addList);
                    }
                    if (count($needDeleted)) {
                        $currentEpisodeShowingsRelation = $currentAssetRelation[$episodeId] ?? [];
                        $currentEpisodeShowingsRelation = array_column($currentEpisodeShowingsRelation, null, 'showings_number');

                        $needDeletedId = array_filter($currentEpisodeShowingsRelation, function ($item) use ($needDeleted) {
                            return in_array($item['showings_number'], $needDeleted);
                        });
                        if (count($needDeletedId) > 0) {
                            model(EntityEpisodeRelationModel::class)->where(['id' => ['IN', array_column($needDeletedId, 'id')]])->delete();
                            $changed += count($needDeletedId);

                        }
                    }
                }
            } else {
                // 删除资产的集关联
                if (count($currentAssetRelation) > 0) {
                    model(EntityEpisodeRelationModel::class)->where(['id' => ['IN', array_column($currentAssetRelation, 'id')]])->delete();
                    $changed += count($currentAssetRelation);
                }
            }
        }
        return $changed;
    }

    /**
     * 概念多集添加重复检查
     * @param $data
     * @throws \Exception
     */
    public function designMultiEpisodeCreateCheck($data)
    {
        $newEpisodeCode = $data['new']['initial_episode_code'] ?? '';
        $newShowingsNumber = $data['new']['showings_number'] ?? '';
        $oldEpisodeCode = $data['old']['initial_episode_code'] ?? '';
        $oldShowingsNumber = $data['old']['showings_number'] ?? '';

        if (!empty($newEpisodeCode)) {
            if ($newEpisodeCode !== $oldEpisodeCode || $newShowingsNumber !== $oldShowingsNumber) {
                $episode = model(EntityModel::class)->where(['project_id' => $data['old']['project_id'], 'module_id' => Module::$moduleDictData['module_index_by_code']['episode']['id'], 'code' => $newEpisodeCode])->field('id')->find();
                if (!empty($episode)) {
                    $addData = [
                        'entity_id' => $data['old']['id'],
                        'episode_id' => $episode['id'],
                        'showings_number' => $newShowingsNumber,
                        'entity_module_id' => Module::$moduleDictData['module_index_by_code']['design']['id'],
                        'project_id' => $data['old']['project_id'],
                    ];
                    model(EntityEpisodeRelationModel::class)->addItem($addData);
                    // 同步概念的集关系到资产
                    Client::send('sync_design_episode_relation_to_asset', build_queue_data_with_xu(['filter' => ['project_id' => $data['old']['project_id'], 'id' => $data['old']['id']]]));

                    $insertData = [[
                        "project_id" => $data['old']['project_id'],
                        "episode_id" => $episode['id'],
                        "name" => $data['new']['name'],
                        "design_category" => $data['new']['design_category'] ?? '',
                        "showings_number" => $newShowingsNumber,
                        "design_perform_species" => $param['data']['design_perform_species'] ?? "",
                    ]];
                    //同步清单数据到剧本中
                    Client::send('fill_back_script_detailed_list_data', build_queue_data_with_xu(['data' => $insertData]));
                }
            } else {
                throw new LogicException("episode showings number same", ErrorCode::DATA_ALREADY_EXISTS);
            }
        }
    }

    /**
     * 获取最大序列号
     * @param $param
     * @return array|string
     */
    public function getMaxSequenceName($param)
    {
        $motionFilter = [
            "JSON_UNQUOTE( JSON_EXTRACT(`json`, '$.sequence_type' ) )" => "motion_capture",
            "module_id" => module_by_code('sequence')['id'],
        ];

        $filter = $param['filter'] ?? [];
        $projectId = $filter['project_id'];
        $filter = array_merge($filter, $motionFilter);

        $project = model(ProjectModel::class)->field('id,project_template_id')->find($projectId);

        if (empty($project)) {
            return [];
        }

        $template = TemplateService::getInstance()->getProjectTemplateModuleFieldConfig($project['project_template_id']);
        $sequenceFields = $template['sequence']['children'];

        //默认限制范围
        $numberPadLengthMax = 4;
        $numberPadLengthMin = 3;
        foreach ($sequenceFields as $sequenceField) {
            if ($sequenceField['code'] === 'name') {
                if ($sequenceField['number_pad_length_max'] != 0 && $sequenceField['number_pad_length_min'] != 0) {
                    $numberPadLengthMax = $sequenceField['number_pad_length_max'];
                    $numberPadLengthMin = $sequenceField['number_pad_length_min'];
                }

            }
        }

        if ($numberPadLengthMax <= 0) {
            throw new LogicException('project template option sequence field limit need greater than 0', ErrorCode::PROJECT_TEMPLATE_ENTITY_FIELD_LENGTH_LIMIT);
        }

        $numberLimit = $numberPadLengthMax - 1;
        $limit = "$numberPadLengthMin,$numberLimit";

        if ($numberPadLengthMin > $numberLimit) {
            throw new LogicException("project template option sequence min field limit can't greater than max field limit", ErrorCode::PROJECT_TEMPLATE_ENTITY_FIELD_LENGTH_LIMIT);
        }

        $sequenceNameRegexp = "^seq([0-9]{{$limit}}|[0-9]{{$numberLimit}}([A-Za-z]{0,1}))$";
        $filter['name'] = ['EXP', "REGEXP '{$sequenceNameRegexp}'"];
        $maxSeq = model(EntityModel::class)->where($filter)->field('max(name) as name')->select();

        $maxSeq = $maxSeq[0]['name'];
        //如果寻找不到,给定默认最小值
        $maxSeqNumber = "1";
        if (!empty($maxSeq) && preg_match('/\d+/', $maxSeq, $seqNumber)) {
            $maxSeqNumber = $seqNumber[0];
            $maxSeqNumberLen = strlen($maxSeqNumber);
            $maxSeqNumber = (int)$maxSeqNumber;
            //最大位数不相加
            $maxNum = pow(10, $numberPadLengthMax - 1) - 1;
            if ($maxNum != $maxSeqNumber) {
                $maxSeqNumber += 1;
                $maxSeqNumber = str_pad($maxSeqNumber, $maxSeqNumberLen, "0", STR_PAD_LEFT);
            }
            return strval($maxSeqNumber);
        } else {
            if ($numberPadLengthMin > 0) {
                $maxSeqNumber = str_pad($maxSeqNumber, $numberPadLengthMin, "0", STR_PAD_LEFT);
            }
        }

        return $maxSeqNumber;
    }

    /**
     * 实体待办任务唯一性检查
     * @param int $entityId
     * @param int $projectId
     * @param string $name
     * @param int $oldId
     * @return bool
     */
    public function entityTodoTaskUniqueCheck(int $entityId, int $projectId, string $name, int $oldId = 0)
    {
        $taskCondition = [
            'entity_id' => $entityId,
            'entity_module_id' => module_by_code('todo')['id'],
            'name' => $name,
            'step_type' => TaskModel::STEP_TYPE_ENTITY_TO_DO,
            'project_id' => $projectId,
        ];
        $existId = model(TaskModel::class)->where($taskCondition)->value('id');
        if ($oldId > 0) {
            return $oldId === $existId || intval($existId) === 0;
        } else {
            return intval($existId) === 0;
        }
    }

    /**
     * 创建实体待办任务
     * @param array $data
     * @param int $userId
     * @param int $tenantId
     * @return array
     * @throws Throwable
     */
    public function createTodoTask(array $data, int $userId, int $tenantId)
    {
        $entity = model(EntityModel::class)->field('id,code,project_id,module_id,json')->find($data['entity_id']);
        if (empty($entity)) {
            throw new LogicException("entity not found", ErrorCode::ENTITY_NOT_FOUND);
        }
        if (!$this->entityTodoTaskUniqueCheck($entity['id'], $entity['project_id'], $data['name'])) {
            throw new LogicException('entity asset todo task already exist', ErrorCode::ENTITY_ASSET_TODO_TASK_ALREADY_EXIST);
        }

        $todoData = $this->getTodoTaskCreateData($data, $entity);
        $todoService = TaskMemberService::getInstance();
        $res = $todoService->create($todoData, $userId, $tenantId);
        $this->syncDesignTaskToDownStreamEntity($entity['id']);
        return $res;
    }

    /**
     * 批量创建实体待办任务
     * @param array $dataList
     * @param int $userId
     * @param int $tenantId
     * @return array
     * @throws Exception
     * @throws Throwable
     */
    public function batchCreateTodoTask(array $dataList, int $userId, int $tenantId)
    {
        $res = [];
        foreach ($dataList as $data) {
            if (!empty($data['id'])) {
                $res[] = $this->updateTodoTask($data);
            } else {
                $res[] = $this->createTodoTask($data, $userId, $tenantId);
            }
        }
        return $res;
    }

    /**
     * 更新实体待办任务
     * @param $data
     * @return array|bool
     * @throws \Exception
     */
    public function updateTodoTask($data)
    {
        $entity = model(EntityModel::class)->field('project_id')->find($data['entity_id']);
        if ($this->entityTodoTaskUniqueCheck($data['entity_id'], $entity['project_id'], $data['name'], $data['id'])) {
            return $this->taskModel->modifyItem($data);
        } else {
            return false;
        }
    }

    /**
     * 删除实体待办任务
     * @param int $todoTaskId
     * @return array
     * @throws Exception
     * @throws Throwable
     */
    public function deleteTodoTask(int $todoTaskId)
    {
        $taskMemberService = TaskMemberService::getInstance();
        $deleteRow = model(TaskModel::class)->where(['id' => $todoTaskId, 'entity_module_id' => module_by_code('todo')['id']])->find();
        if (empty($deleteRow)) {
            throw new LogicException("todo task not found", ErrorCode::TASK_NOT_FOUND);
        }
        $entity = model(EntityModel::class)->find($deleteRow['entity_id']);
        $entityModuleCode = module_by_id($entity['module_id'])['code'];
        $entityTaskField = $entityModuleCode . '_task';
        $entityJson = json_decode($entity['json'], true);
        $entityTaskIdList = array_filter(explode(',', $entityJson[$entityTaskField] ?? ''), function ($item) use ($todoTaskId) {
            return !empty($item) && $item != $todoTaskId;
        });

        $updateData = [
            'id' => $entity['id'],
            $entityTaskField => implode(',', $entityTaskIdList),
        ];
        $commonService = CommonService::instance('entity');
        $commonService->setCurrentModuleCode($entityModuleCode);
        $res = [];
        $this->taskModel->startTrans();
        try {
            $res['task_update'] = $taskMemberService->delete($todoTaskId);
            $res['entity_update'] = $commonService->update($updateData, $entityModuleCode, false);
            $this->syncDesignTaskToDownStreamEntity($entity['id']);
            $this->taskModel->commit();
        } catch (Throwable $e) {
            $this->taskModel->rollback();
            throw $e;
        }

        return $res;
    }

    /**
     * 废除实体待办任务
     * @param int $todoTaskId
     * @param int $userId
     * @return array|bool|int|string
     * @throws Exception
     * @throws Throwable
     */
    public function deprecateTodoTask(int $todoTaskId, int $userId)
    {
        $taskMemberService = TaskMemberService::getInstance();
        $deprecateData = [
            'id' => $todoTaskId,
            'code' => 'closed',
            'type' => 'multiple',
        ];
        return $taskMemberService->updateStatus($deprecateData, $userId);
    }

    /**
     * 启用衍生迭代待办
     * @param int $todoTaskId
     * @param int $userId
     * @return array|bool|int|string
     * @throws Exception
     * @throws Throwable
     */
    public function enableTodoTask(int $todoTaskId, int $userId)
    {
        $taskMemberService = TaskMemberService::getInstance();
        $statusService = StatusService::getInstance();
        $taskDefaultStatus = $statusService->getTaskDefaultStatus();
        $deprecateData = [
            'id' => $todoTaskId,
            'code' => $taskDefaultStatus['code'],
            'type' => 'multiple',
        ];
        return $taskMemberService->updateStatus($deprecateData, $userId);
    }

    /**
     * 批量更新 前置检查
     * @param $data
     * @param string $getModuleCode
     * @return void
     */
    private function beforeUpdate(&$data, string $getModuleCode)
    {
        switch ($getModuleCode) {
            case 'storyboard':
                if (isset($data['storyboard_asset'])) {
                    // 说明正在修改关联资产
                    // 检查是否需要修改关联贴图
                    $queryFields = [
                        'storyboard_asset',
                        'storyboard_task',
                    ];
                    $oldData = $this->entityModel
                        ->alias('storyboard')
                        ->where(['id' => $data['id']])
                        ->field(transform_custom_fields($queryFields, $getModuleCode))
                        ->find();
                    // 关联清空
                    if (empty($data['storyboard_asset'])) {
                        $data['storyboard_task'] = '';
                    } else {
                        // 修改关联
                        $oldStoryboardTaskIdList = array_filter(explode(',', $oldData['storyboard_task']));
                        // 和已修改的资产进行交集  获得剩下的待办任务id
                        if (!empty($oldStoryboardTaskIdList)) {
                            $storyboardTaskList = $this->taskModel
                                ->join('entity e on e.id = task.entity_id')
                                ->where(['task.id' => ['IN', $oldStoryboardTaskIdList]])
                                ->field('task.id,e.code')
                                ->select();
                            if (!empty($storyboardTaskList)) {
                                $wantRelateAssetCode = model(EntityModel::class)
                                    ->where([
                                        'id' => ['IN', $data['storyboard_asset']],
                                    ])
                                    ->column('code');
                                $storyboardTaskList = array_filter($storyboardTaskList, function ($item) use ($wantRelateAssetCode) {
                                    return in_array($item['code'], $wantRelateAssetCode);
                                });
                                $newStoryboardTaskIds = implode(',', array_column($storyboardTaskList, 'id'));
                                if ($newStoryboardTaskIds != $oldData['storyboard_task']) {
                                    $data['storyboard_task'] = $newStoryboardTaskIds;
                                }
                            }
                        }
                    }

                }
                break;
        }
    }

    /**
     * 同步概念迭代实体到下游实体
     * @param int $designId
     * @return array|bool
     * @throws Exception
     */
    public function syncDesignTaskToDownStreamEntity(int $designId)
    {
        $design = $this->entityModel
            ->where([
                'id' => $designId,
                'module_id' => module_by_code('design')['id'],
            ])
            ->find();
        if (empty($design)) {
            throw new LogicException('entity not found', ErrorCode::ENTITY_NOT_FOUND);
        }
        $downStreamEntity = $this->entityModel
            ->join('module m on m.id = entity.module_id')
            ->where(['project_id' => $design['project_id']])
            ->where("JSON_UNQUOTE(JSON_EXTRACT(json,CONCAT('$.',m.code,'_design'))) = $designId")
            ->field('entity.id,m.code as module_code')
            ->find();
        if (empty($downStreamEntity)) {
            // 还没发布
            return false;
        }
        $designTask = json_decode($design['json'], true)['design_task'] ?? '';
        return CommonService::instance('entity')
            ->setCurrentModuleCode($downStreamEntity['module_code'])
            ->update(['id' => $downStreamEntity['id'], $downStreamEntity['module_code'] . '_task' => $designTask], false);
    }

    /**
     * 生产实体待办创建数据
     * @param $data
     * @param $entity
     * @return array|array[]
     */
    private function getTodoTaskCreateData($data, $entity)
    {
        $todoData = [
            'task' => [
                'name' => $data['name'],
                'entity_id' => $entity['id'],
                'project_id' => $entity['project_id'],
                'step_type' => TaskModel::STEP_TYPE_ENTITY_TO_DO,
            ],
        ];
        $entityModuleCode = module_by_id($entity['module_id'])['code'];
        $taskIdList = json_decode($entity['json'], true)[$entityModuleCode . '_task'] ?? '';
        if (!empty($taskIdList)) {
            $taskMembers = model(TaskMemberModel::class)
                ->where(['task_id' => ['IN', $taskIdList]])
                ->select();
            $executors = array_column($taskMembers, 'user_id', 'user_id');
            $partners = array_column($taskMembers, 'partner', 'partner');
            $userIdList = array_merge($executors, $partners);
            unset($userIdList[0]);
            if (!empty($userIdList)) {
                $userList = model(UserModel::class)->where(['id' => ['IN', $userIdList]])->select();
                $userList = array_column($userList, 'union_id', 'id');
                foreach ($executors as $executor) {
                    if (!empty($userList[$executor])) {
                        $todoData['executor'][] = $userList[$executor];
                    }
                }
                foreach ($partners as $partner) {
                    if (!empty($userList[$partner])) {
                        $todoData['partner'][] = $userList[$partner];
                    }
                }
            }
        } else {
            $asset = model(EntityModel::class)
                ->where([
                    'project_id' => $entity['project_id'],
                    'module_id' => ['IN', [module_by_code('asset')['id'], module_by_code('level')['id']]],
                    'code' => $entity['code'],
                ])->field('id')->find();
            if (!empty($asset)) {
                $texStepExecutorList = model(TaskModel::class)
                    ->join('step on step.id = task.step_id')
                    ->where([
                            'task.project_id' => $entity['project_id'],
                            'task.step_type' => 'step',
                            'step.code' => ['IN', OptionsService::getInstance()->getTexStepCode()],
                            'task.entity_id' => $asset['id'],
                        ]
                    )
                    ->column('executor');
                $texStepExecutorList = array_filter($texStepExecutorList);
                if (!empty($texStepExecutorList)) {
                    $texStepExecutorList = model(UserModel::class)->where(['id' => ['IN', $texStepExecutorList]])->column('union_id');
                    if (!empty($texStepExecutorList)) {
                        $todoData['executor'] = $texStepExecutorList;
                    }
                }
            }
        }
        return $todoData;
    }

    /**
     * 查询实体的生成下游实体信息
     * @param array $entity
     * @return array|false|mixed|string|null
     */
    public function getDownstreamEntity(array $entity)
    {
        $filter = [];
        $entityModuleCode = module_by_id($entity['module_id'])['code'];
        switch ($entityModuleCode) {
            case 'design':
                $filter = [
                    'project_id' => $entity['project_id'],
                    'code' => $entity['code'],
                ];
                if (empty($entity['design_category'])) {
                    if (empty($entity['json'])) {
                        throw new LogicException('missing json field to check category', ErrorCode::CATEGORY_NOT_FOUND);
                    }
                    if (is_string($entity['json'])) {
                        $entityJson = json_decode($entity['json'], true);
                    } else {
                        $entityJson = $entity['json'];
                    }
                    $designCategory = $entityJson['design_category'];
                } else {
                    $designCategory = $entity['design_category'];
                }
                if ($designCategory == '关卡') {
                    $filter['module_id'] = module_by_code('level')['id'];
                } else {
                    $filter['module_id'] = module_by_code('asset')['id'];
                }
                break;
            case 'storyboard':
                $filter = [
                    'project_id' => $entity['project_id'],
                    'name' => $entity['name'],
                    'showings_number' => $entity['showings_number'],
                    'initial_episode_code' => $entity['initial_episode_code'],
                    'module_id' => module_by_code('shot')['id'],
                ];
                break;
        }

        if (empty($filter)) {
            return null;
        }
        return model(EntityModel::class)->where($filter)->find();
    }

    /**
     * 获取当前实体下的所有工序
     * @param $entityId
     * @return array|false|mixed|string
     */
    public function findEntityStepList($entityId)
    {
        $entity = model(EntityModel::class)->field("id,module_id")->find($entityId);
        if (empty($entity)) {
            return [];
        }
        $parentStepCategory = model(StepCategoryModel::class)
            ->where(["entity_id" => $entity["module_id"], 'parent_id' => 0])
            ->field("id")
            ->find();
        if (empty($parentStepCategory)) {
            return [];
        }
        $stepCategoryList = model(StepCategoryModel::class)->where(["parent_id" => $parentStepCategory['id']])->field("id")->select();
        $stepCategoryIds = array_column($stepCategoryList, "id");
        return model(StepModel::class)->where(["step_category_id" => ["IN", $stepCategoryIds]])->field("id,name")->select();
    }


    /**
     * 查询当前实体的上游实体
     * shot         =>  storyboard
     * asset,level  =>  design
     * @param $entity
     * @return array
     */
    public function getUpstreamEntity($entity)
    {
        $entityModuleCode = module_by_id($entity['module_id'])['code'];
        if (!in_array($entityModuleCode, ['asset', 'level', 'shot'])) {
            return null;
        }
        if (in_array($entityModuleCode, ['asset', 'level'])) {
            $upstreamEntityField = $entityModuleCode . '_design';
        } else {
            $upstreamEntityField = $entityModuleCode . '_storyboard';
        }
        if (empty($entity[$upstreamEntityField])) {
            $entityJsonFields = json_decode($entity['json'] ?? '{}', true);
            if (empty($entityJsonFields[$upstreamEntityField])) {
                return null;
            }
            $entity[$upstreamEntityField] = $entityJsonFields[$upstreamEntityField];
        }
        return model(EntityModel::class)->find($entity[$upstreamEntityField]);
    }

    /**
     * 查询实体信息 design,asset,level  shot,storyboard  sequence
     * @param $entity
     * @return array
     */
    public function getEntityScoped($entity)
    {
        $res = [$entity];
        $entityModuleCode = module_by_id($entity['module_id'])['code'];
        if (in_array($entityModuleCode, ['asset', 'level', 'shot'])) {
            $upstreamEntity = $this->getUpstreamEntity($entity);
            if (!empty($upstreamEntity)) {
                $res[] = $upstreamEntity;
            }
        } else if (in_array($entityModuleCode, ['storyboard', 'design'])) {
            $downstreamEntity = $this->getDownstreamEntity($entity);
            if (!empty($downstreamEntity)) {
                $res[] = $downstreamEntity;
            }
        }
        return $res;
    }

    /**
     * 查询资产线被外包的实体
     * @param array $entity
     * @return array|false|mixed|string|null
     */
    public function getAssetLevelEntityOutSourceEntityScopedList(array $entity)
    {
        $outsourceProjectIdList = model(ProjectModel::class)
            ->where(['belong_project_id' => $entity['project_id']])
            ->column('id');
        if (empty($outsourceProjectIdList)) {
            return null;
        }
        return model(EntityModel::class)
            ->where([
                'project_id' => ['IN', $outsourceProjectIdList],
                'module_id' => ['IN', [$entity['module_id'], module_by_code('design')['id']]],
                'code' => $entity['code'],
            ])
            ->select();
    }

    /**
     * 通过实体查找对应的工序任务进度
     * @param $filter
     * @param $moduleCode
     * @return array|mixed|string
     */
    public function findEntityStepTaskSchedule($filter, $moduleCode)
    {
        // 资产概念只需要Code码和项目ID即可
        if (in_array($moduleCode, ['design', 'asset', 'level'])) {
            $entityQueryFilter = [
                "project_id" => $filter['project_id'],
                "module_id" => module_by_code('design')['id'],
                "code" => $filter['entity_code'],
            ];
        } elseif (in_array($moduleCode, ['storyboard', 'shot'])) {
            $entityQueryFilter = [
                "project_id" => $filter['project_id'],
                "module_id" => module_by_code("storyboard")['id'],
                "code" => $filter['entity_code'],
                "initial_episode_code" => $filter['initial_episode_code'],
                "showings_number" => $filter['showings_number'],
            ];
        } else {
            return [];
        }
        $entity = model(EntityModel::class)->where($entityQueryFilter)->find();
        if (empty($entity)) {
            return [];
        }
        $entityStepTaskList = model(TaskModel::class)
            ->alias('t')
            ->field("t.id,t.name,t.assign_status,s.name as task_status,t.plan_end_time,t.end_time")
            ->join("status s on s.id = json_unquote(json_extract(t.json,'$.task_status'))")
            ->where(['t.source_entity_id' => $entity['id'], 't.tenant_id' => $entity['tenant_id'], 't.step_type' => TaskModel::STEP_TYPE_STEP])
            ->order('t.id ASC')
            ->select();
        if (empty($entityStepTaskList)) {
            return [];
        }
        $taskIds = array_column($entityStepTaskList, 'id');
        //版本查询
        $maxVersion = VersionService::getInstance()->getTaskMaxVersionInfo($taskIds, "id,number,link_id,json");
        $versionMediaMap = [];
        $mediaIds = [];
        foreach ($maxVersion as $maxVersionItem) {
            $versionJson = json_decode($maxVersionItem['json'], true);
            if (isset($versionJson['version_media'])) {
                $versionMediaIds = explode(',', $versionJson['version_media']);
                $versionMediaMap[$maxVersionItem['id']] = $versionMediaIds;
                foreach ($versionMediaIds as $versionMediaId) {
                    if (in_array($versionMediaId, $mediaIds)) {
                        continue;
                    }
                    $mediaIds[] = $versionMediaId;
                }
            } else {
                $versionMediaMap[$maxVersionItem['id']] = [];
            }
        }
        $versionMediaData = [];
        if (!empty($mediaIds)) {
            $versionMediaData = MediaService::getInstance()->selectMediaParamMap($mediaIds);
        }

        $maxVersionLinkTaskIds = array_column($maxVersion, null, 'link_id');

        // 查询最后一个工单
        $maxTaskWorkOrder = model(TaskWorkOrderModel::class)->field("MAX(id) as id,task_id")
            ->where(['task_id' => ['IN', $taskIds]])
            ->group('task_id')
            ->select();
        $maxTaskWorkOrderMap = array_column($maxTaskWorkOrder, null, 'task_id');

        foreach ($entityStepTaskList as &$item) {
            $version = $maxVersionLinkTaskIds[$item['id']] ?? null;
            $item['version_media'] = [];
            $item['version'] = empty($version) ? null : $version['number'];
            $item['task_work_order_id'] = $maxTaskWorkOrderMap[$item['id']]['id'] ?? null;
            if (empty($version) || empty($versionMediaMap[$version['id']])) {
                continue;
            }
            foreach ($versionMediaMap[$version['id']] as $mediaId) {
                if (!isset($versionMediaData[$mediaId])) {
                    continue;
                }
                $item['version_media'][] = $versionMediaData[$mediaId];
            }
        }
        return $entityStepTaskList;
    }

    /**
     * 搜索剧集实体工序列表
     * @param $param
     * @param string $moduleCode
     * @return array
     */
    public function searchEpisodeEntityStepList($param, $moduleCode = 'asset')
    {
        $page = $param["page"] ?? [1, C('default_page_total')];
        $filter = $param["filter"] ?? [];
        //查询范围限定
        $moduleIds = [];
        if ($moduleCode === 'asset') {
            $currentModuleIds = [module_by_code("design")["id"]];
        } else {
            $moduleIds[] = module_by_code("episode")["id"];
            $currentModuleIds = [module_by_code("storyboard")["id"]];
        }
        $moduleIds = array_merge($currentModuleIds, $moduleIds);
        $closedStatusData = StatusService::getInstance()->getTaskDefaultClosedStatus();
        if (isset($filter["task.name"])) {
            if (empty($filter["task.name"])) {
                unset($filter["task.name"]);
            }

            if ($moduleCode === 'asset') {
                $currentModuleIds = array_merge($currentModuleIds, [module_by_code("asset")["id"], module_by_code("level")["id"]]);
            } else {
                $currentModuleIds = array_merge($currentModuleIds, [module_by_code("shot")["id"]]);
            }

            $result = model(EntityModel::class)
                ->join("module on entity.module_id = module.id")
                ->join("task on task.entity_id = entity.id")
                ->where($filter)
                ->where([
                    "entity.module_id" => ["IN", $currentModuleIds],
                    "task.entity_module_id" => ["IN", $currentModuleIds],
                    "step_type" => "step",
                    "_string" => " JSON_UNQUOTE(json_extract( task.json, '$.is_dismantle_task' )) is NULL AND (json_extract(entity.json,concat( '$.', module.code, '_status' )) is NULL
	OR json_extract(entity.json,concat( '$.', module.code, '_status' )) <> $closedStatusData)",
                ])
                ->page(...$page)
                ->field([
                    "task.id", "entity.initial_episode_code", "entity.showings_number", "entity.name,task.name as task_name", "entity.id as entity_id",
                    "entity.module_id",
                    "JSON_UNQUOTE(JSON_EXTRACT(entity.json, CONCAT('$.',module.code,'_design'))) as design",
                    "JSON_UNQUOTE(JSON_EXTRACT(entity.json, '$.shot_storyboard')) as shot_storyboard",
                ])
                ->select();

            $result = array_map(function ($resultItem) {
                if (in_array($resultItem["module_id"], [module_by_code("asset")["id"], module_by_code("level")["id"]])) {
                    $resultItem["entity_id"] = intval($resultItem["design"]);
                } elseif ($resultItem["module_id"] === module_by_code("shot")["id"]) {
                    $resultItem["entity_id"] = intval($resultItem["shot_storyboard"]);
                }
                unset($resultItem["design"], $resultItem["shot_storyboard"]);
                $resultItem["relation_type"] = "step";
                return $resultItem;
            }, $result);

        } else {
            $result = model(EntityModel::class)
                ->join("module on entity.module_id = module.id")
                ->where($filter)
                ->where(["module_id" => ["IN", $moduleIds], "_string" => "(json_extract(entity.json,concat( '$.', module.code, '_status' )) is NULL
	OR json_extract(entity.json,concat( '$.', module.code, '_status' )) <> $closedStatusData)"])
                ->field("entity.id,entity.project_id,entity.code,entity.name,entity.module_id,entity.initial_episode_code,entity.showings_number")
                ->page(...$page)->select();


            $designCodes = [];
            $storyboardFilter = [];
            $projectId = 0;
            $result = array_map(function ($resultItem) use (&$designCodes, &$storyboardFilter, &$projectId) {
                if (empty($projectId)) {
                    $projectId = $resultItem["project_id"];
                }

                if ($resultItem["module_id"] == module_by_code("episode")["id"]) {
                    $resultItem["relation_type"] = "episode";
                } else {
                    $resultItem["relation_type"] = "entity";
                    if ($resultItem["module_id"] == module_by_code("design")["id"]) {
                        $designCodes[] = $resultItem["code"];
                    }

                    if ($resultItem["module_id"] == module_by_code("storyboard")["id"]) {
                        $storyboardFilter[] = [
                            "entity.initial_episode_code" => $resultItem["initial_episode_code"],
                            "entity.showings_number" => $resultItem["showings_number"],
                            "entity.code" => $resultItem["code"],
                        ];
                    }
                }
                return $resultItem;
            }, $result);

            //查找任务
            $resultData = [];
            if (!empty($designCodes)) {
                $designList = model(EntityModel::class)
                    ->join("module on entity.module_id = module.id")
                    ->join("task on task.entity_id = entity.id")
                    ->where([
                        "entity.code" => ["IN", $designCodes],
                        "entity.project_id" => $projectId,
                        "task.entity_module_id" => ["IN", array_merge($currentModuleIds, [module_by_code("asset")["id"], module_by_code("level")["id"]])],
                        "step_type" => "step",
                        "_string" => "JSON_UNQUOTE(json_extract( task.json, '$.is_dismantle_task' )) is NULL",
                    ])
                    ->field(["task.id", "entity.code", "entity.initial_episode_code", "entity.showings_number", "entity.name,task.name as task_name", "entity.id as entity_id",
                        "entity.module_id",
                        "JSON_UNQUOTE(JSON_EXTRACT(entity.json, CONCAT('$.',module.code,'_design'))) as design",])
                    ->select();

                $designList = array_map(function ($designItem) {
                    if (in_array($designItem["module_id"], [module_by_code("asset")["id"], module_by_code("level")["id"]])) {
                        $designItem["entity_id"] = intval($designItem["design"]);
                    }
                    $designItem["relation_type"] = "step";
                    unset($designItem["design"]);
                    return $designItem;
                }, $designList);

                $designList = array_group_by($designList, "code");

                foreach ($result as $resultItem) {
                    $resultData[] = $resultItem;
                    if (isset($designList[$resultItem["code"]])) {
                        foreach ($designList[$resultItem["code"]] as $designTaskItem) {
                            $resultData[] = $designTaskItem;
                        }
                    }
                }
            }

            // TODO 待优化
            if (!empty($storyboardFilter)) {
                $storyboardFilter["_logic"] = "OR";
                $storyboardList = model(EntityModel::class)
                    ->join("module on entity.module_id = module.id")
                    ->join("task on task.entity_id = entity.id")
                    ->where([
                        $storyboardFilter,
                        "entity.project_id" => $projectId,
                        "task.entity_module_id" => ["IN", array_merge($currentModuleIds, [module_by_code("shot")["id"]])],
                        "step_type" => "step",
                        "_string" => "JSON_UNQUOTE(json_extract( task.json, '$.is_dismantle_task' )) is NULL",
                    ])
                    ->field(["task.id", "entity.code", "entity.initial_episode_code", "entity.showings_number", "entity.name,task.name as task_name", "entity.id as entity_id",
                        "entity.module_id",])
                    ->select();

                $storyboardData = [];
                foreach ($storyboardList as $storyboardItem) {
                    $storyboardItem["relation_type"] = "step";
                    $storyboardData[$storyboardItem["initial_episode_code"]][$storyboardItem["showings_number"]][$storyboardItem["code"]][] = $storyboardItem;
                }

                foreach ($result as $resultItem) {
                    $resultData[] = $resultItem;
                    if (isset($storyboardData[$resultItem["initial_episode_code"]][$resultItem["showings_number"]][$resultItem["code"]])) {
                        foreach ($storyboardData[$resultItem["initial_episode_code"]][$resultItem["showings_number"]][$resultItem["code"]] as $storyboardDataItem) {
                            $resultData[] = $storyboardDataItem;
                        }
                    }
                }
            }

            if (!empty($resultData)) {
                $result = $resultData;
            }

        }

        return $result;

    }

    /**
     * 实体启用或废除发送消息
     * @param $entity
     * @param $status
     * @return void
     */
    public function entityAbolitionOrEnablingSendMessage($entity, $status)
    {
        try {
            $chatService = new ChatService();
            $chat = $chatService->createEntityChat($entity);
            if (empty($chat)) {
                return;
            }
            $message['type'] = 'change_entity_task_status_message';
            $message['replace'] = ['status' => $status];
            $message = $chatService->getEntityTaskMessageTemplate($message, request()->getUserId(), $entity);
            $chatService->pushEntityTaskMessage($message, $chat);
        } catch (Throwable $e) {
            trace(format_exception_info($e), 'ERR');
        }
    }

    /**
     * 查询当前实体的上游实体
     * shot         =>  storyboard
     * asset,level  =>  design
     * @param $entityList
     * @param $entityModuleCode
     * @return array
     */
    public function getUpstreamEntityIdList($entityList, $entityModuleCode)
    {
        $res = [];
        if (!in_array($entityModuleCode, ['asset', 'level', 'shot'])) {
            return null;
        }
        if (in_array($entityModuleCode, ['asset', 'level'])) {
            $upstreamEntityField = $entityModuleCode . '_design';
        } else {
            $upstreamEntityField = $entityModuleCode . '_storyboard';
        }
        foreach ($entityList as $entity) {
            $entityJsonFields = json_decode($entity['json'] ?? '{}', true);
            if (empty($entityJsonFields[$upstreamEntityField])) {
                continue;
            }
            $res[] = $entityJsonFields[$upstreamEntityField];
        }
        return $res;
    }

    /**
     * 查询实体的生成下游实体信息
     * @param array $entityList
     * @param $entityModuleCode
     * @return array|false|mixed|string|null
     */
    public function getDownstreamEntityIdList(array $entityList, $entityModuleCode)
    {
        $filter = [];

        switch ($entityModuleCode) {
            case 'design':
                $filter = [
                    'project_id' => $entityList[0]['project_id'],
                    'name' => ['in', array_column($entityList, 'code')],
                ];
                $filter['module_id'] = ['IN', [module_by_code('asset')['id'], module_by_code('level')['id']]];
                break;
            case 'storyboard':
                $filter = [
                    'project_id' => $entityList[0]['project_id'],
                    'module_id' => module_by_code('shot')['id'],
                ];
                $shotFilter = [];
                foreach ($entityList as $entity) {
                    $shotFilter[] = [
                        'name' => $entity['name'],
                        'showings_number' => $entity['showings_number'],
                        'initial_episode_code' => $entity['initial_episode_code'],
                    ];
                }
                $shotFilter['_logic'] = 'or';
                $filter[] = $shotFilter;
                break;
        }

        if (empty($filter)) {
            return null;
        }
        return model(EntityModel::class)->where($filter)->field('id')->column('id');
    }

    /**
     * 查询实体的生成下游实体信息
     * @param array $entityList
     * @param $entityModuleCode
     * @return array
     */
    public function getDownstreamEntityIdMap(array $entityList, $entityModuleCode)
    {
        $filter = [];
        $fields = [];
        switch ($entityModuleCode) {
            case 'design':
                $filter = [
                    'entity.project_id' => $entityList[0]['project_id'],
                    'entity.name' => ['in', array_column($entityList, 'name')],
                ];
                $filter['entity.module_id'] = ['IN', [module_by_code('asset')['id'], module_by_code('level')['id']]];
                $fields = ["entity.id", "JSON_UNQUOTE(JSON_EXTRACT(entity.json,concat('$.',module.code,'_design'))) as upstream_entity_id"];
                break;
            case 'storyboard':
                $filter = [
                    'entity.project_id' => $entityList[0]['project_id'],
                    'entity.module_id' => module_by_code('shot')['id'],
                ];
                $shotFilter = [];
                foreach ($entityList as $entity) {
                    $shotFilter[] = [
                        'entity.name' => $entity['name'],
                        'entity.showings_number' => $entity['showings_number'],
                        'entity.initial_episode_code' => $entity['initial_episode_code'],
                    ];
                }
                if (count($shotFilter) > 1) {
                    $shotFilter['_logic'] = 'or';
                }
                $filter[] = $shotFilter;
                $fields = ["entity.id", "JSON_UNQUOTE(JSON_EXTRACT(entity.json,concat('$.',module.code,'_storyboard'))) as upstream_entity_id"];
                break;
        }

        if (empty($filter)) {
            return null;
        }
        $res = model(EntityModel::class)->join('module on module.id = entity.module_id')->where($filter)->field($fields)->select();

        return array_column($res, 'id', 'upstream_entity_id');
    }

    /**
     * 查询实体信息 design,asset,level  shot,storyboard  sequence
     * @param $entityList
     * @return array
     */
    public function getEntityIdListScoped($entityList)
    {
        $entityListGrouped = array_group_by($entityList, 'module_id');
        $res = array_column($entityList, 'id');
        foreach ($entityListGrouped as $moduleId => $entityList) {
            $entityModuleCode = module_by_id($moduleId)['code'];
            if (in_array($entityModuleCode, ['asset', 'level', 'shot'])) {
                $upstreamEntityIdList = $this->getUpstreamEntityIdList($entityList, $entityModuleCode);
                if (!empty($upstreamEntityIdList)) {
                    $res = array_merge($res, $upstreamEntityIdList);
                }
            } else if (in_array($entityModuleCode, ['storyboard', 'design'])) {
                $downstreamEntityIdList = $this->getDownstreamEntityIdList($entityList, $entityModuleCode);
                if (!empty($downstreamEntityIdList)) {
                    $res = array_merge($res, $downstreamEntityIdList);
                }
            }
        }
        return $res;
    }

    /**
     * 设置剧集封板和播出
     * @param $currentModuleData
     * @param $type
     * @param $data
     * @return array|bool|null
     * @throws Exception
     */
    public function setEpisodeSealOrShow($currentModuleData, $type, $data)
    {
        $commonService = CommonService::instance("entity");
        $commonService->setCurrentModuleCode($currentModuleData);
        $episodeData = $commonService->find(['filter' => ['id' => $data['id']]]);
        if (empty($episodeData)) {
            throw_strack_exception("Episode is not exits", ErrorCode::ENTITY_NOT_FOUND);
        }
        $statusList = StatusService::getInstance()->getList(['code' => ['IN', 'inprogress,final']], '*');
        $statusList = array_column($statusList, null, 'code');
        if ($type == 'seal') {
            $updateData = [
                'id' => $data['id'],
                'episode_is_seal' => $data['is_seal'],
                'episode_seal_time' => $data['is_seal'] == 'yes' ? strtotime(date("Y-m-d", time())) : 0,
                'episode_status' => $data['is_seal'] == 'yes' ? $statusList['final']['id'] : $statusList['inprogress']['id'],
            ];
        } else if ($type == 'show') {
            $updateData = [
                'id' => $data['id'],
                'episode_is_show' => $data['is_show'],
                'episode_show_time' => $data['is_show'] == 'yes' ? strtotime(date("Y-m-d", time())) : 0,
            ];
        }
        return $commonService->update($updateData, $currentModuleData, false);
    }

    /**
     *  查询未播出的剧集列表
     * @param $projectId
     * @return array|false|mixed|string
     */
    public function getNotShowEpisodeList($projectId)
    {
        // 查询出没有播放的剧集
        return model(EntityModel::class)
            ->alias('episode')
            ->where(['episode.project_id' => $projectId, 'episode.module_id' => module_by_code('episode')['id']])
            ->where("(episode.json->>'$.episode_is_show' is null or episode.json->>'$.episode_is_show'='no') and (episode.json->>'$.episode_is_delete' is null or episode.json->>'$.episode_is_delete'='no')")
            ->where(['episode.code' => ['LIKE', 'EP%']])
            ->select();
    }

    /**
     * 需求管理列表过滤条件
     * @param $param
     * @param $moduleCode
     * @return array[]|mixed
     */
    public function getNoteManageListFilter($param, $moduleCode)
    {
        $filter = $param['filter'];
        $entityId = $param['entity_id'] ?? '';
        $projectId = $param['project_id'] ?? '';
        $episodeId = $param['episode_id'] ?? ''; // 集id
        $entityName = $param['entity_name'] ?? '';
        $shotStatus = $param['shot_status'] ?? []; // 镜头状态
        $designGrade = $param['design_grade'] ?? []; // 资产等级
        $assetStatus = $param['asset_status'] ?? []; // 资产状态
        $showingsNumber = $param['showings_number'] ?? ''; // 场次
        $designCategory = $param['design_category'] ?? []; // 资产类型
        $initialEpisodeCode = $param['initial_episode_code'] ?? []; // 初始集
        $initialEpisodeCodeList = $param['initial_episode_code_list'] ?? ''; // 剧集,场次筛选
        $getAssetUseInfo = $param['get_asset_use_info'] ?? false; // 获取资产使用情况
        $storyboardRelation = $param['storyboard_relation'] ?? false; // 镜头关联
        $designDemandStatus = $param['design_demand_status'] ?? ''; // 资产需求状态
        $storyboardDemandStatus = $param['storyboard_demand_status'] ?? ''; // 镜头需求状态
        $episodeType = $param['episode_type'] ?? ''; // 集类型 本集制作/本集使用

        $entityInfo = [];
        if (!empty($entityId)) {
            $entityInfo = model(EntityModel::class)->where(['id' => $entityId])
                ->field("id,name,code,json ->> '$.design_category' as design_category")
                ->find();
        }

        if (!empty($initialEpisodeCodeList)) { // 剧集,场次筛选
            if ($episodeType == 'episode_use') { // 本集使用
                $codes = array_column($initialEpisodeCodeList, 'initial_episode_code');
                $episodeCode = model(EntityModel::class)
                    ->where(['project_id' => $projectId, 'code' => ['IN', $codes], 'module_id' => module_by_code('episode')['id']])
                    ->field('id,code')
                    ->select(); // 查询集id
                $episodeCodeMap = array_column($episodeCode, null, 'code');
                $initialEpisodeCodeList = array_map(function ($tmp) use ($episodeCodeMap) {
                    $tmp['episode_id'] = $episodeCodeMap[$tmp['initial_episode_code']]['id'] ?? 0;
                    return $tmp;
                }, $initialEpisodeCodeList);
                $relationSql = EpisodeAllocationService::getInstance()->getEpisodeShowingsSql($initialEpisodeCodeList, 'episode_id', 'showings_number');
                $relationEntityIds = model(EntityEpisodeRelationModel::class)
                    ->where(['entity_module_id' => module_by_code('design')['id']])
                    ->where(['project_id' => $projectId])
                    ->where(['_string' => $relationSql])
                    ->column('entity_id');
                if (!empty($relationEntityIds)) {
                    $filter[] = [
                        'entity.id' => ['IN', $relationEntityIds],
                    ];
                }
            } else { // 默认初始集-本集制作
                $sql = EpisodeAllocationService::getInstance()->getEpisodeShowingsSql($initialEpisodeCodeList, 'initial_episode_code', 'showings_number');
                $filter[] = ['_string' => $sql];
            }
        }
        if (!empty($moduleCode)) {
            $filter[] = [
                'module.id' => module_by_code($moduleCode)['id'],
            ];
        }
        if (!empty($designDemandStatus)) { // 资产需求状态
            $filter[] = [
                "json ->> '$.design_demand_status'" => ['IN', $designDemandStatus],
            ];
        }
        if (!empty($storyboardDemandStatus)) { // 镜头资产需求状态
            $filter[] = [
                "json ->> '$.storyboard_demand_status'" => ['IN', $storyboardDemandStatus],
            ];
        }
        if (!empty($entityName)) {
            $filter[] = [
                'entity.name' => [
                    'like', '%' . $entityName . '%',
                ],
            ];
        }
        if (!empty($projectId)) {
            $filter[] = [
                'entity.project_id' => $projectId,
            ];
        }
        if (!empty($initialEpisodeCode)) {
            $filter[] = [
                'entity.initial_episode_code' => ['IN', join(',', $initialEpisodeCode)],
            ];
        }
        if (!empty($showingsNumber)) {
            $filter[] = [
                'entity.showings_number' => $showingsNumber,
            ];
        }
        if (!empty($assetStatus)) {
            $statusCode = model(StatusModel::class)->where(['code' => ['IN', join(',', $assetStatus)]])->column('id');
            $filter[] = [
                "entity.json ->> '$.design_status'" => ['IN', join(',', $statusCode)],
            ];
        }
        if (!empty($designGrade)) {
            $filter[] = [
                "entity.json ->> '$.design_grade'" => ['IN', join(',', $designGrade)],
            ];
        }
        if (!empty($shotStatus)) {
            $statusCode = model(StatusModel::class)->where(['code' => ['IN', join(',', $shotStatus)]])->column('id');
            $filter[] = [
                "entity.json ->> '$.storyboard_status'" => ['IN', join(',', $statusCode)],
            ];
        }
        if (!empty($designCategory)) {
            $filter[] = [
                "entity.json ->> '$.design_category'" => ['IN', join(',', $designCategory)],
            ];
        }
        if (!empty($episodeId)) {
            $episodeData = model(EntityModel::class)->where(['id' => $episodeId])->field('id,code')->find();
            $filter[] = [
                "entity.initial_episode_code" => $episodeData['code'],
            ];
        }
        // 查询实体使用情况 - [角色,道具] [场景] [关卡]
        if ($getAssetUseInfo) {
            if (empty($entityInfo['design_category'])) {
                return [];
            }
            if (in_array($entityInfo['design_category'], ['角色', '道具'])) { // 通过[角色,道具]找到当前资产下关联的镜头
                $entityData = model(EntityModel::class)->where(['id' => $entityId])->field('id,project_id,code')->find();
                $assetEntityData = model(EntityModel::class)
                    ->where(['project_id' => $entityData['project_id'], 'code' => $entityData['code'], 'module_id' => module_by_code('asset')['id']])
                    ->field('id,code,project_id')->find();
                $assetEntityId = $assetEntityData['id'];

                $shotIds = model(EntityModel::class)
                    ->where(['project_id' => $entityData['project_id'], 'entity.initial_episode_code' => ['IN', join(',', $initialEpisodeCode)], 'module_id' => module_by_code('storyboard')['id']])
                    ->where("find_in_set({$assetEntityId},json->>'$.storyboard_asset')")
                    ->column('id');

                if (empty($shotIds)) {
                    return [];
                }
                $filter = [
                    'entity.id' => ['IN', $shotIds],
                ];
            }
            if ($entityInfo['design_category'] == '场景') { // 通过[场景]找到当前资产下关联的关卡

                $scenarioName = $entityInfo['name'];
                $levelIds = model(EntityModel::class)
                    ->where(['project_id' => $projectId, 'module_id' => module_by_code('design')['id']])
                    ->where(["json->>'$.design_category'" => '关卡'])
                    ->where("find_in_set('{$scenarioName}',json->>'$.level_relate_asset')")
                    ->column('id');
                if (empty($levelIds)) {
                    return [];
                }
                $filter = [
                    'entity.id' => ['IN', $levelIds],
                ];
            }
            if ($entityInfo['design_category'] == '关卡') { // 查询关卡下的场景

                $designData = model(EntityModel::class)
                    ->where(['id' => $entityId])
                    ->field("id,code,project_id")
                    ->find();
                $levelData = model(EntityModel::class)
                    ->where(['project_id' => $designData['project_id'], 'module_id' => module_by_code('level')['id']])
                    ->where(['code' => $designData['code']])
                    ->field("json ->> '$.level_asset' as level_asset_ids")
                    ->find();
                if (empty($levelData['level_asset_ids'])) {
                    return [];
                }

                //通过资产找概念
                $levelAssetDesignIds = model(EntityModel::class)
                    ->join("module on module.id = entity.module_id")
                    ->where([
                        'entity.id' => ["IN", $levelData['level_asset_ids']],
                    ])->field("JSON_UNQUOTE(JSON_EXTRACT(entity.json, CONCAT('$.',module.code,'_design'))) as design_id")->column("design_id");

                $filter = [
                    'entity.id' => ['IN', $levelAssetDesignIds],
                ];
            }
        }
        if ($storyboardRelation) { // 查看镜头关联

            $entityAssetData = model(EntityModel::class)
                ->where(['id' => $entityId])
                ->field("module_id,project_id,tenant_id,code,initial_episode_code,showings_number,json ->> '$.storyboard_asset' as storyboard_asset_ids,json ->> '$.storyboard_level' as storyboard_level_ids")
                ->find();

            // 如若是shot,则转换成 storyboard
            if ($entityAssetData['module_id'] == module_by_code('shot')['id']) {
                $entityAssetData = model(EntityModel::class)
                    ->where([
                        'project_id' => $entityAssetData['project_id'],
                        'tenant_id' => $entityAssetData['tenant_id'],
                        'code' => $entityAssetData['code'],
                        'initial_episode_code' => $entityAssetData['initial_episode_code'],
                        'showings_number' => $entityAssetData['showings_number'],
                        'module_id' => module_by_code('storyboard')['id'],
                    ])
                    ->field("json ->> '$.storyboard_asset' as storyboard_asset_ids,json ->> '$.storyboard_level' as storyboard_level_ids")
                    ->find();
            }

            $entityAssetIds = '';
            if (!empty($entityAssetData['storyboard_asset_ids'])) {
                $entityAssetIds .= $entityAssetData['storyboard_asset_ids'];
            }
            if (!empty($entityAssetData['storyboard_level_ids'])) {
                $entityAssetIds .= ',' . $entityAssetData['storyboard_level_ids'];
            }
            $entityAssetIds = trim($entityAssetIds, ',');
            if (empty($entityAssetIds)) {
                return [];
            }
            $entityRelationList = model(EntityModel::class)
                ->where(['id' => ['IN', $entityAssetIds]])
                ->field('id,module_id,json')->select();
            if (empty($entityRelationList)) {
                return [];
            }
            $assetIds = [];
            foreach ($entityRelationList as $entityRelationItem) {
                $entityRelationItemJson = json_decode($entityRelationItem['json'], true);
                $entityRelationItemModuleCode = module_by_id($entityRelationItem['module_id'])['code'];
                if (!empty($entityRelationItemJson[$entityRelationItemModuleCode . '_design'])) {
                    $assetIds[] = $entityRelationItemJson[$entityRelationItemModuleCode . '_design'];
                }
            }
            if (empty($assetIds)) {
                return [];
            }
            $filter = [
                'entity.id' => ['IN', $assetIds],
            ];
        }
        return $filter;
    }

    /**
     * 获取实体,镜头使用情况
     * @param $entityIds
     * @return array
     */
    public function getUseInfo($entityIds)
    {

        $entityData = model(EntityModel::class)->join('module on entity.module_id = module.id')
            ->where(['entity.id' => ['IN', $entityIds]])
            ->field("entity.id,entity.json->> '$.storyboard_asset' as storyboard_asset_ids,entity.json ->> '$.storyboard_level' as storyboard_level_ids,entity.module_id,entity.project_id,entity.code,entity.name,module.code as module_code,entity.json->>'$.design_category' as design_category,entity.json ->> '$.level_relate_asset' as level_relate_asset")
            ->select();
        $entityMap = array_group_by($entityData, 'design_category');
        $rolePropsEntity = [];
        if (!empty($entityMap['角色'])) {
            $rolePropsEntity = array_merge($rolePropsEntity, $entityMap['角色']);
        }
        if (!empty($entityMap['道具'])) {
            $rolePropsEntity = array_merge($rolePropsEntity, $entityMap['道具']);
        }
        // 处理[角色]和[道具]关联的[集]
        $rolePropsUseInfo = [];
        if (!empty($rolePropsEntity)) {
            $rolePropsUseInfo = $this->rolePropsUseInfo($rolePropsEntity);
        }
        // 处理场景关联的[关卡]
        $scenarioUseInfo = [];
        if (!empty($entityMap['场景'])) {
            $scenarioUseInfo = $this->scenarioUseInfo($entityMap['场景']);
        }
        $levelScenarioUseInfo = [];
        $levelEpisodeUseInfo = [];
        if (!empty($entityMap['关卡'])) {
            $levelEpisodeUseInfo = $this->levelEpisodeUseInfo($entityMap['关卡']);  // 处理关卡关联的[集]
            $levelScenarioUseInfo = $this->levelScenarioUseInfo($entityMap['关卡']); // 处理关卡关联的[场景]
        }
        // 处理[镜头]关联的[资产]
        $shotUseInfo = [];
        $moduleCodeMap = array_group_by($entityData, 'module_code');
        if (!empty($moduleCodeMap['storyboard'])) {
            $shotUseInfo = $this->showUseInfo($moduleCodeMap['storyboard']);
        }

        $useInfoMap = array_merge_recursive($rolePropsUseInfo, $scenarioUseInfo, $levelScenarioUseInfo, $levelEpisodeUseInfo, $shotUseInfo);
        return array_map(function ($tmp) use ($useInfoMap) {
            $tmp['use_info'] = $useInfoMap["entity-" . $tmp['id']] ?? [];
            unset($tmp['design_category'], $tmp['level_relate_asset'], $tmp['storyboard_asset_ids'], $tmp['storyboard_level_ids']);
            return $tmp;
        }, $entityData);
    }


    /**
     * 角色,道具 使用情况
     * @param $rolePropsEntity
     * @return array
     */
    public function rolePropsUseInfo($rolePropsEntity)
    {

        $projectId = $rolePropsEntity[0]['project_id'] ?? 0;
        $designCodes = array_column($rolePropsEntity, 'code');
        $assetEntityData = model(EntityModel::class)
            ->where(['module_id' => module_by_code('asset')['id'], 'project_id' => $projectId])
            ->where(['code' => ['IN', $designCodes]])
            ->field("id,code,json ->> '$.asset_design' as asset_design")
            ->select();
        if (empty($assetEntityData)) {
            return [];
        }
        // 查出概览生成的资产绑定到一个数组里,方便匹配关联的镜头
        $assetEntityMap = array_column($assetEntityData, null, 'asset_design');
        $rolePropsEntity = array_map(function ($tmp) use ($assetEntityMap) {
            $tmp['asset_design'] = $assetEntityMap[$tmp['id']]['id'] ?? 0;
            return $tmp;
        }, $rolePropsEntity);

        // 通过[角色,道具]找到当前资产下关联的镜头->集
        $assetEntityIds = array_column($assetEntityData, 'id');
        $where = '(';
        foreach ($assetEntityIds as $assetEntityId) {
            $where .= "find_in_set({$assetEntityId},json->>'$.storyboard_asset')";
            $where .= ' OR ';
        }
        $where = trim($where, ' OR');
        $where .= ')';

        $shotData = model(EntityModel::class)
            ->where(['project_id' => $projectId, 'module_id' => module_by_code('storyboard')['id']])
            ->where($where)
            ->field("id,code,project_id,module_id,initial_episode_code,json ->> '$.storyboard_asset' as storyboard_asset")
            ->group('initial_episode_code')
            ->select();
        if (empty($shotData)) {
            return [];
        }
        $shotData = array_map(function ($tmp) {
            if (!empty($tmp['storyboard_asset'])) {
                $tmp['storyboard_asset'] = explode(',', $tmp['storyboard_asset']);
            }
            return $tmp;
        }, $shotData);

        // 计算每个实体关联的镜头数量->角色,道具
        foreach ($rolePropsEntity as &$rolePropsEntityItem) {
            $num = 0;
            foreach ($shotData as $shotDataItem) {
                if (!empty($rolePropsEntityItem['asset_design']) && !empty($shotDataItem['storyboard_asset'])) {
                    if (in_array($rolePropsEntityItem['asset_design'], $shotDataItem['storyboard_asset'])) {
                        $num++;
                    }
                }
            }
            $rolePropsEntityItem['num'] = $num;
            unset($num);
        }
        $returnData = [];
        foreach ($rolePropsEntity as $item) {
            if (empty($item['num'])) {
                continue;
            }
            $returnData["entity-" . $item['id']] = [['type' => 'episode', 'num' => $item['num']]];
            unset($item['num']);
        }
        return $returnData;
    }

    /**
     * 场景 使用情况
     * @param $data
     * @return array
     */
    public function scenarioUseInfo($data)
    {
        // 通过[场景]找到当前资产下关联的关卡
        $projectId = $data[0]['project_id'] ?? 0;
        $where = '(';
        foreach ($data as $scenarioName) {
            $where .= "find_in_set('{$scenarioName['name']}',json->>'$.level_relate_asset')";
            $where .= ' OR ';
        }
        $where = trim($where, ' OR');
        $where .= ')';
        $levelData = model(EntityModel::class)
            ->where(['project_id' => $projectId, 'module_id' => module_by_code('design')['id'], "json->>'$.design_category'" => '关卡'])
            ->where($where)
            ->field("id,code,json->>'$.level_relate_asset' as level_relate_asset")
            ->select();
        $levelData = array_map(function ($tmp) {
            if (!empty($tmp['level_relate_asset'])) {
                $tmp['level_relate_asset'] = explode(',', $tmp['level_relate_asset']);
            }
            return $tmp;
        }, $levelData);
        if (empty($levelData)) {
            return [];
        }
        // 计算每个每个场景下的关卡
        foreach ($data as &$dataItem) {
            $num = 0;
            foreach ($levelData as $levelDataItem) {
                if (!empty($dataItem['name']) && !empty($levelDataItem['level_relate_asset'])) {
                    if (in_array($dataItem['name'], $levelDataItem['level_relate_asset'])) {
                        $num++;
                    }
                }
            }
            $dataItem['num'] = $num;
        }

        $returnData = [];
        foreach ($data as $item) {
            if (empty($item['num'])) {
                continue;
            }
            $returnData["entity-" . $item['id']] = [['type' => 'level', 'num' => $item['num']]];
        }
        return $returnData;
    }


    /**
     * 关卡使用的场景
     * @param $entityData
     * @return array
     */
    public function levelScenarioUseInfo($entityData)
    {
        // 查询关卡下的场景
        if (empty($entityData)) {
            return [];
        }
        $entityData = array_map(function ($tmp) {
            if (!empty($tmp['level_relate_asset'])) {
                $tmp['level_relate_asset'] = explode(',', trim($tmp['level_relate_asset'], ','));
                $tmp['num'] = count($tmp['level_relate_asset']);
            }
            return $tmp;
        }, $entityData);

        $returnData = [];
        foreach ($entityData as $item) {
            if (empty($item['num'])) {
                continue;
            }
            $returnData["entity-" . $item['id']] = [['type' => 'scenario', 'num' => $item['num']]];
        }
        return $returnData;
    }

    /**
     * 关卡使用的集
     * @param $entityData
     * @return array
     */
    public function levelEpisodeUseInfo($entityData)
    {
        $projectId = $entityData[0]['project_id'] ?? 0;
        $designCodes = array_column($entityData, 'code');

        $assetEntityData = model(EntityModel::class)
            ->where(['module_id' => module_by_code('level')['id'], 'project_id' => $projectId])
            ->where(['code' => ['IN', $designCodes]])
            ->field("id,code,json ->> '$.level_design' as level_design")
            ->select();
        if (empty($assetEntityData)) {
            return [];
        }

        $assetEntityIds = array_column($assetEntityData, 'id');
        $where = '(';
        foreach ($assetEntityIds as $assetEntityId) {
            $where .= "find_in_set({$assetEntityId},json->>'$.storyboard_level')";
            $where .= ' OR ';
        }
        $where = trim($where, ' OR');
        $where .= ')';
        $shotData = model(EntityModel::class)
            ->where(['project_id' => $projectId, 'module_id' => module_by_code('storyboard')['id']])
            ->where($where)
            ->field("id,code,project_id,module_id,json ->> '$.storyboard_level' as storyboard_level")
            ->select();
        if (empty($shotData)) {
            return [];
        }
        $shotData = array_map(function ($tmp) {
            if (!empty($tmp['storyboard_level'])) {
                $tmp['storyboard_level'] = explode(',', trim($tmp['storyboard_level'], ','));
            }
            return $tmp;
        }, $shotData);

        // 计算关卡关联的[集]
        foreach ($assetEntityData as &$assetEntityItem) {
            $num = 0;
            foreach ($shotData as $shotItem) {
                if (!empty($assetEntityItem['id']) && !empty($shotItem['storyboard_level'])) {
                    if (in_array($assetEntityItem['id'], $shotItem['storyboard_level'])) {
                        $num++;
                    }
                }
            }
            $assetEntityItem['num'] = $num;
        }
        $returnData = [];
        foreach ($assetEntityData as $item) {
            if (empty($item['num'])) {
                continue;
            }
            $returnData["entity-" . $item['level_design']] = [['type' => 'episode', 'num' => $item['num']]];
        }
        return $returnData;
    }

    /**
     * 镜头使用情况
     * @return array[]
     */
    public function showUseInfo($entityData)
    {
        if (empty($entityData)) {
            return [];
        }
        $entityData = array_map(function ($tmp) {
            $assetNum = 0;
            $levelNum = 0;
            if (!empty($tmp['storyboard_asset_ids'])) {
                $storyboardAssetIds = explode(',', trim($tmp['storyboard_asset_ids'], ','));
                $assetNum = count($storyboardAssetIds);
            }
            if (!empty($tmp['storyboard_level_ids'])) {
                $storyboardLevelIds = explode(',', trim($tmp['storyboard_level_ids'], ','));
                $levelNum = count($storyboardLevelIds);
            }
            $tmp['num'] = $assetNum + $levelNum;
            return $tmp;
        }, $entityData);
        $returnData = [];
        foreach ($entityData as $item) {
            if (empty($item['num'])) {
                continue;
            }
            $returnData["entity-" . $item['id']] = [['type' => 'asset', 'num' => $item['num']]];
        }
        return $returnData;
    }


    /**
     * 使用集与制作集格式查询
     * @param $initialEpisodeCodeList
     * @param $episodeType
     * @param $projectId
     * @return string
     */
    public function episodeFormatQuery($initialEpisodeCodeList, $episodeType, $projectId)
    {
        if (empty($initialEpisodeCodeList)) { // 剧集,场次筛选
            return "";
        }

        if ($episodeType == 'episode_use') { // 本集使用
            $codes = array_column($initialEpisodeCodeList, 'initial_episode_code');
            $episodeCode = model(EntityModel::class)
                ->where(['project_id' => $projectId, 'code' => ['IN', $codes], 'module_id' => module_by_code('episode')['id']])
                ->field('id,code')
                ->select(); // 查询集id
            $episodeCodeMap = array_column($episodeCode, null, 'code');
            $initialEpisodeCodeList = array_map(function ($tmp) use ($episodeCodeMap) {
                $tmp['episode_id'] = $episodeCodeMap[$tmp['initial_episode_code']]['id'] ?? 0;
                return $tmp;
            }, $initialEpisodeCodeList);
            $sql = EpisodeAllocationService::getInstance()->getEpisodeShowingsSql($initialEpisodeCodeList, 'episode_id', 'showings_number');
        } else { // 默认初始集-本集制作
            $sql = EpisodeAllocationService::getInstance()->getEpisodeShowingsSql($initialEpisodeCodeList, 'initial_episode_code', 'showings_number');
        }

        return $sql;
    }


    /**
     * 获取实体清单任务进度列表
     * @param $filter
     * @param $moduleCode
     * @return array|false|mixed|string
     */
    public function getEntityStepTaskScheduleList($filter, $moduleCode)
    {
        // 资产概念只需要Code码和项目ID即可
        if (in_array($moduleCode, ['design', 'asset', 'level'])) {
            $queryFilter = [
                "code" => $filter['entity_code'],
                "project_id" => $filter['project_id'],
                "module_id" => ["IN", [module_by_code('design')['id'], module_by_code("level")['id'], module_by_code("asset")['id']]],
            ];
        } else if (in_array($moduleCode, ['storyboard', 'shot'])) {
            $queryFilter = [
                "code" => $filter['entity_code'],
                "project_id" => $filter['project_id'],
                "initial_episode_code" => $filter['initial_episode_code'],
                "showings_number" => $filter['showings_number'],
                "module_id" => module_by_code("shot")['id'],

            ];
        } else {
            return [];
        }

        $entity = model(EntityModel::class)->where($queryFilter)->field("id")->find();

        if (empty($entity)) {
            return [];
        }

        $cancelStatus = StatusService::getInstance()->getTaskDefaultClosedStatus();
        return model(TaskModel::class)
            ->where([
                'entity_id' => $entity['id'],
                "step_type" => 'step',
                "json->>'$.task_status'" => ['NEQ', $cancelStatus['id']],
            ])
            ->field("id,name")
            ->select();
    }

    /**
     * 获取资产关联的镜头列表
     * @param $entityId
     * @return array
     */
    public function findAssetRelationShotList($entityId)
    {
        // 判定是 概念 , 资产 , 关卡
        $entity = model(EntityModel::class)->find($entityId);
        $moduleId = $entity['module_id'];

        $moduleCode = module_by_id($moduleId)['code'];
        if ($moduleCode == 'design') {
            $entity = model(EntityModel::class)->where([
                'project_id' => $entity['project_id'],
                'code' => $entity['code'],
                'module_id' => ["IN", [module_by_code("asset")["id"], module_by_code("level")["id"]]],
            ])->find();

            if (empty($entity)) {
                return [];
            }

            $moduleCode = module_by_id($entity['module_id'])['code'];
        }

        //查询镜头
        $shotList = model(EntityModel::class)->where([
            'project_id' => $entity['project_id'],
            'module_id' => module_by_code("shot")['id'],
            "JSON_UNQUOTE(JSON_EXTRACT(json, CONCAT('$.shot_','{$moduleCode}')))" => ['FIND_IN_SET', $entity['id']],
        ])->field("initial_episode_code,count(id) as count")->group("initial_episode_code")->select();

        if (empty($shotList)) {
            return [];
        }

        $initialEpisodeCode = [];
        $shotMap = [];
        foreach ($shotList as $shotItem) {
            $initialEpisodeCode[] = $shotItem['initial_episode_code'];
            $shotMap[$shotItem['initial_episode_code']] = $shotItem;
        }

        //获取剧集的状态
        $episodeList = model(EntityModel::class)
            ->where([
                'project_id' => $entity['project_id'],
                'code' => ["IN", $initialEpisodeCode],
                'module_id' => module_by_code('episode')['id'],
            ])
            ->field("id,code,json_unquote(json_extract(json, '$.episode_is_seal')) as episode_is_seal,json_unquote(json_extract(json, '$.episode_status')) as episode_status")
            ->select();

        $episodeStatusList = [];
        $episodeIds = [];
        foreach ($episodeList as $episodeItem) {
            if (!empty($episodeItem['episode_status'])) {
                $episodeStatusList[] = $episodeItem['episode_status'];
            }
            $episodeIds[] = $episodeItem['id'];
        }

        $statusList = [];
        if (!empty($episodeStatusList)) {
            $statusList = model(StatusModel::class)->where([
                'id' => ["IN", $episodeStatusList],
            ])->field("id,name,color,code,correspond,icon")->select();
            $statusList = array_column($statusList, null, 'id');
        }

        // 获取动作的排期时间
        $stepCategory = model(StepCategoryModel::class)->where(['code' => 'shot_motion'])->find();

        $planList = model(PlanModel::class)
            ->join("join task_relation_tag on task_relation_tag.task_id = plan.task_id and task_relation_tag.link_module_code in ('step_category','show_step_category') ")
            ->where([
                "plan.project_id" => $entity['project_id'],
                "plan.entity_id" => ["IN", $episodeIds],
                "plan.scope" => PlanModel::SCOPE_SINGLE,
                "plan.type" => PlanModel::TYPE_STEP_CATEGORY,
                "task_relation_tag.link_id" => $stepCategory['id'],
            ])
            ->field("entity_id,end_time")
            ->select();
        $planList = array_column($planList, null, 'entity_id');


        return array_map(function ($episodeItem) use ($statusList, $shotMap, $planList) {
            $episodeItem['count'] = $shotMap[$episodeItem['code']]['count'] ?? 0;
            $episodeItem['status'] = $statusList[$episodeItem['episode_status']] ?? null;
            $episodeItem['end_time'] = $planList[$episodeItem['id']] ?? null;
            return $episodeItem;
        }, $episodeList);
    }

    /**
     * 任务卡片-查询资产关联
     * @param $param
     * @param $moduleCode
     * @return array
     */
    public function getRelationAsset($param, $moduleCode)
    {
        $relationFilter = $this->getNoteManageListFilter($param, $moduleCode); // 关联过滤条件
        if (empty($relationFilter)) {
            return [];
        }
        $categoryName = $param['category_name'] ?? null;
        if (!empty($categoryName)) {
            $param['filter'][] = [
                "JSON_UNQUOTE(JSON_EXTRACT(entity.json, CONCAT('$.',module.code,'_category')))" => ['IN', $categoryName],
            ];
        }
        $queryFields = [
            'entity.id',
            'entity.name',
            'entity.initial_episode_code',
            'entity.showings_number',
            'entity.json',
            'entity.project_id',
            'entity.plan_start_time',
            'entity.plan_end_time',
            'entity.remark',
            'module.code',
        ];

        $page = $param['page'] ?? [1, C("database.database_max_select_rows")];
        $entityData = model(EntityModel::class)
            ->join('module on entity.module_id = module.id')
            ->field(build_complex_query_fields($queryFields, '__'))
            ->where($relationFilter)
            ->where($param['filter'])
            ->page($page[0], $page[1])
            ->select();
        if (empty($entityData)) {
            return [];
        }
        $countList = model(EntityModel::class)
            ->join('module on entity.module_id = module.id')
            ->field("count(*) as count, JSON_UNQUOTE(JSON_EXTRACT(entity.json, CONCAT('$.', module.code, '_category'))) as category")
            ->where($relationFilter)
            ->group('category')
            ->select();

        //获取到关联时间
        $relationshipMap = [];
        if (((isset($param['get_asset_use_info']) && $param['get_asset_use_info']) ||
                (isset($param['storyboard_relation']) && $param['storyboard_relation']))
            && !empty($param['entity_id'])) {
            $relationshipMap = EntityRelationshipService::getInstance()->getUpEntityRelationshipInfo($param['entity_id']);
        }


        $statusMap = StatusService::getInstance()->getList([], '*');
        $statusMap = array_column($statusMap, null, 'id');
        $returnList = array_map(function ($tmp) use ($statusMap, $relationshipMap) {
            $tmp = separate_complex_query_data($tmp, '__');
            $entityJsonArr = json_decode($tmp['entity']['json'], true);
            $entityCategoryIdx = $tmp['module']['code'] . '_category';
            $entityGradeIdx = $tmp['module']['code'] . '_grade';
            $entityStatusIdx = $tmp['module']['code'] . '_status';
            $tmp['entity']['category'] = $entityJsonArr[$entityCategoryIdx] ?? null;
            $tmp['entity']['grade'] = $entityJsonArr[$entityGradeIdx] ?? null;
            $entityStatus = $entityJsonArr[$entityStatusIdx] ?? null;
            $tmp['entity']['status'] = $statusMap[$entityStatus] ?? null;
            //关联修改时间
            $tmp['entity']['relevance_modify_time'] = $relationshipMap[$tmp['entity']['id']]['created'] ?? 0;
            //关联修改人
            $tmp['entity']['relevance_modify_man'] = $relationshipMap[$tmp['entity']['id']]['created_by'] ?? null;
            unset($tmp['entity']['json']);
            return $tmp;
        }, $entityData);

        return [
            'list' => $returnList,
            'cont_list' => $countList,
        ];
    }

    /**
     * 实体状态检查 通过检查任务的完成情况 来更新实体状态
     *
     * @param integer $entityId
     * @return array | null
     */
    public function statusCheck(int $entityId)
    {
        $entity = model(EntityModel::class)
            ->where([
                'id' => $entityId,
                'module_id' => ['IN', [module_by_code('design')['id'], module_by_code('storyboard')['id']]],
            ])
            ->find();
        if (empty($entity)) {
            throw new LogicException('entity not found', ErrorCode::ENTITY_NOT_FOUND);
        }
        $closedStatus = StatusService::getInstance()->getTaskDefaultClosedStatus();
        $doneStatus = StatusService::getInstance()->getTaskDoneStatus();
        $waitPublishStatus = StatusService::getInstance()->getTaskReadyToPublishStatus();

        $waitForGenerateTask = model(StatusModel::class)->where(['code' => 'wait_for_generated'])->find();
        $waitForGenerateMergeSequence = model(StatusModel::class)->where(['code' => 'wait_for_merge_sequence'])->find();
        $draftStatus = StatusService::getInstance()->getDefaultDraftStatus();

        $entityModule = module_by_id($entity['module_id']);
        $entity['json'] = json_decode($entity['json'], true);
        $entityStatus = $entity['json'][$entityModule['code'] . '_status'] ?? 0;
        if (in_array($entityStatus, [$closedStatus['id'], $waitPublishStatus['id'], $waitForGenerateTask['id'], $waitForGenerateMergeSequence['id'], $draftStatus['id']])) {
            throw new LogicException('entity is not published or closed', ErrorCode::DATA_INVALID);
        }

        $taskList = model(TaskModel::class)
            ->where(['source_entity_id' => $entityId, 'step_type' => TaskModel::STEP_TYPE_STEP],)
            ->field("id,json->>'$.task_status' task_status")->select();

        $statusList = StatusService::getInstance()->getList([], 'id,correspond');
        $statusList = array_column($statusList, null, 'id');
        $allClosed = true;
        $allDone = true;
        $started = false;
        foreach ($taskList as $task) {
            $taskStatus = $task['task_status'] ?? 0;
            $taskStatusOb = $statusList[$taskStatus] ?? null;
            if (empty($taskStatusOb)) {
                continue;
            }
            if ($taskStatusOb['correspond'] != 'blocked') {
                $allClosed = false;
            } else {
                // 已取消任务不进行完成/进行中判断
                continue;
            }
            if ($taskStatusOb['correspond'] != 'publish') {
                $allDone = false;
            }
            // 任务不属于未开始 说明开始过
            if ($taskStatusOb['correspond'] != 'not_started') {
                $started = true;
            }
        }

        APP_DEBUG && trace(var_export(['all_closed' => $allClosed, 'all_done' => $allDone, 'started' => $started, 'entity_status' => $entityStatus], true), 'DEBUG');
        $downstreamEntity = $this->getDownstreamEntity($entity);
        if ($allClosed) {
            // 所有任务都废除了 那么手动变更实体状态为已取消 不需要删除实体 只标记为已取消即可
            $allEntityIdList = [$entityId];
            if (!empty($downstreamEntity)) {
                $allEntityIdList[] = $downstreamEntity['id'];
            }
            EntityAbolitionOrEnableService::getInstance()->setEntityAndChildrenStatus('closed', $allEntityIdList);
            return $closedStatus;
        }
        $updateStatus = null;
        if ($allDone) {
            $updateStatus = $doneStatus;
        } else if ($started) {
            $inprogressStatus = StatusService::getInstance()->getTaskDefaultStartStatus();
            $updateStatus = $inprogressStatus;
        } else {
            $updateStatus = StatusService::getInstance()->getOne(['code' => 'task_generated'], '*');
        }
        if ($updateStatus['id'] == $entityStatus) {
            return null;
        }
        $this->entityModel->execute("update entity set json = json_set(json, '$.{$entityModule['code']}_status', {$updateStatus['id']}) where id = $entityId");
        if (!empty($downstreamEntity)) {
            $entityModule = module_by_id($downstreamEntity['module_id']);
            $this->entityModel->execute("update entity set json = json_set(json, '$.{$entityModule['code']}_status', {$updateStatus['id']}) where id = {$downstreamEntity['id']}");
        }
        return $updateStatus;
    }

    /**
     * 查询外包实体
     * @param array $entity
     * @return array|false|mixed|string|void|null
     */
    public function getOutsourceEntity(array $entity)
    {
        $outsourceProjectIdList = model(ProjectModel::class)
            ->where(['belong_project_id' => $entity['project_id']])
            ->column('id');
        if (empty($outsourceProjectIdList)) {
            return null;
        }
        $entityModule = module_by_id($entity['module_id']);
        switch ($entityModule['code']) {
            case 'level':
            case 'asset':
                return model(EntityModel::class)
                    ->where([
                        'project_id' => ['IN', $outsourceProjectIdList],
                        'module_id' => ['IN', [$entity['module_id'], module_by_code('design')['id']]],
                        'code' => $entity['code'],
                    ])
                    ->select();

            case 'shot':
                return model(EntityModel::class)
                    ->where([
                        'project_id' => ['IN', $outsourceProjectIdList],
                        'module_id' => ['IN', [$entity['module_id'], module_by_code('storyboard')['id']]],
                        'initial_episode_code' => $entity['initial_episode_code'],
                        'showings_number' => $entity['showings_number'],
                        'code' => $entity['code'],
                    ])
                    ->select();

        }
    }

    /**
     * 查询实体关联的任务
     * @param array $entity
     * @return array|false|mixed|string
     */
    public function getEntityRelateTask(array $entity, $queryFields = [])
    {
        $moduleCode = module_by_id($entity['module_id'])['code'];
        $taskFilter = [
            'entity.tenant_id' => $entity['tenant_id'],
            'entity.project_id' => $entity['project_id'],
        ];
        // 资产线
        if (in_array($moduleCode, ['level', 'asset', 'design'])) {
            $taskFilter[] = [
                'entity.module_id' => ['IN', [module_by_code('design')['id'], $entity['module_id']]],
                'entity.code' => $entity['code'],
            ];
        } else {
            // 镜头线
            $taskFilter[] = [
                'entity.module_id' => $entity['module_id'],
                'entity.id' => $entity['id'],
            ];
        }
        if (empty($queryFields)) {
            $queryFields = [
                'task.id',
                'task.tenant_id',
                'task.department_id',
                'task.assignee_producer',
                'task.assignee',
                'task.executor_type',
                'task.executor',
            ];
        }
        return model(EntityModel::class)
            ->join('task on task.entity_id = entity.id')
            ->field($queryFields)
            ->where($taskFilter)
            ->select();
    }

    /**
     * 查询关联的人员
     * @param array $entity
     * @return array
     * @throws \think\Exception
     */
    public function getEntityRelatePeople(array $entity, $withHeadman = true)
    {
        $queryFields = [
            'task.id',
            'task.tenant_id',
            'task.department_id',
            'task.assignee_producer',
            'task.assignee',
            'task.executor_type',
            'task.executor',
        ];
        $taskList = self::getEntityRelateTask($entity, $queryFields);
        $outsideExecuteTaskList = [];
        foreach ($taskList as $task) {
            if (in_array($task['executor_type'], TaskModel::OUTSIDE_EXECUTE_TYPE_GROUP)) {
                $outsideExecuteTaskList[] = $task;
            }
        }

        $orderTaskList = [];
        if (!empty($outsideExecuteTaskList)) {
            $outsideTaskListQueryFields = $queryFields;
            $outsideTaskListQueryFields[] = 'otl.task_id as inside_task_id';
            $orderTaskList = model(OrderTaskListModel::class)
                ->alias('otl')
                ->join('task on task.id = otl.real_task_id')
                ->where(['otl.task_id' => ['IN', array_column($outsideExecuteTaskList, 'id')]])
                ->field($outsideTaskListQueryFields)
                ->select();
            $orderTaskList = array_column($orderTaskList, null, 'inside_task_id');
        }
        $userIdList = [];
        $tenantIdList = [];
        $departmentIdList = [];
        foreach ($taskList as $task) {
            if (in_array($task['executor_type'], TaskModel::OUTSIDE_EXECUTE_TYPE_GROUP)) {
                $task = $orderTaskList[$task['id']] ?? null;
                if (empty($task)) {
                    continue;
                }
            }
            if ($task['tenant_id'] > 0) {
                $tenantIdList[$task['tenant_id']] = $task['tenant_id'];
            }
            if ($task['assignee_producer'] > 0) {
                $userIdList[$task['assignee_producer']] = $task['assignee_producer'];
            }
            if ($task['executor'] > 0) {
                $userIdList[$task['executor']] = $task['executor'];
            }
            if ($task['department_id'] > 0) {
                $departmentIdList[$task['department_id']] = $task['department_id'];
            }
        }
        $userList = [];
        if (!empty($userIdList)) {
            $userList = model(UserModel::class)->where(['id' => ['IN', implode(',', $userIdList)]])->select();
            $userList = array_column($userList, null, 'id');
        }
        if (!empty($departmentIdList) && $withHeadman) {
            $filter = [
                'tenant_id' => ['-in', implode(',', $tenantIdList)],
                'department_id' => ['-in', implode(',', $departmentIdList)],
                'role_code' => TaskService::HEADMAN_CODE,
            ];
            $userDepartmentMap = UserService::getInstance()->getDepartmentSaasUserMap($filter);
            foreach ($userDepartmentMap as $departmentUserList) {
                $userList = array_merge($userList, array_column($departmentUserList, null, 'id'));
            }
        }
        return $userList;
    }

    /**
     * 剧集sql 处理
     * @param array $initialEpisodeCodeList
     * @param string $initialEpisodeKey
     * @param string $showingsKey
     * @return string
     */
    public function initialEpisodeCodeSql(array $initialEpisodeCodeList, string $initialEpisodeKey, string $showingsKey): string
    {
        $sqlArr = array_map(function ($initialEpisodeCode) use ($initialEpisodeKey, $showingsKey) {
            $showingsSql = " `" . $initialEpisodeKey . "` = '" . $initialEpisodeCode['initial_episode_code'] . "' ";
            if (!empty($initialEpisodeCode['showings_number'])) {
                $showingsNumberList = array_map('strtoupper', $initialEpisodeCode['showings_number']);
                $showingsSql .= " AND UPPER(" . $showingsKey . ") IN ('" . implode("','", $showingsNumberList) . "') ";
            }
            return $showingsSql;
        }, $initialEpisodeCodeList);

        if (empty($sqlArr)) {
            $sqlArr = ['1=1'];
        }
        return ' (( ' . implode(' ) OR ( ', $sqlArr) . ' ))';
    }

    /**
     *
     * 获取异常实体列表环节状态数据
     * @param array $entityIds 传多个实体id数组
     * @return array ["entity_id":"实体id","step_category_id":"环节id","step_category_status":"环节完成状态"]
     */
    public function getAbnormalEntityStepCategoryList(array $entityIds): array
    {
        if (empty($entityIds) || count($entityIds) > 100) {
            return [];
        }
        ## 前端根据异常实体entity_id 获取环节任务完成情况 （非已取消和已完成的任务状态）
        $fields = [
            'id', 'name', 'entity_id', 'step_category_id', "JSON_UNQUOTE(JSON_EXTRACT(task.json,'$.task_status')) as task_status"
        ];
        $statusIds = StatusService::getInstance()->byCodeMap(['closed', 'final'], true, 'id');
        $entityStepCategoryTaskList = model(TaskModel::class)
            ->where(['entity_id' => ['IN', $entityIds], 'task.step_type' => TaskModel::STEP_TYPE_STEP])
            ->where(["JSON_UNQUOTE(JSON_EXTRACT(task.json,'$.task_status'))" => ['IN', $statusIds]])
            ->field($fields)
            ->limit(1000)
            ->select();
        if (empty($entityStepCategoryTaskList)) {
            return [];
        }
        return $entityStepCategoryTaskList;
    }


    /**
     * 获取异常任务延期确认数据(排除制片通过或者拒绝)
     * @param array $deliveryTime [1696521600, 1796521600] 任务的计划交付时间范围
     * @param array $taskIds [1,2,3]所属实体类型
     * @return array
     */
    public function getAbnormalEntityDelayConfirmList(array $deliveryTime, array $taskIds): array
    {
        if (empty($taskIds) || count($taskIds) > 1000) {
            return [];
        }
        $field = [
            'dotsc.is_active',             ## 是否当前确认环节
            'dotsc.id as dotcc',           ## 延期确认记录id
            'dotsc.is_confirmed',          ## 是否已确认
            'dotsc.step_category_id',      ## 延期环节确认id
            'dotsc.is_self_step_category', ## 当前确认记录是否发起环节
            'dot.task_id',                 ## 延期任务id
            'dot.plan_delivery_time',      ## 原计划交付时间
            'dot.delay_order_id as delay_order_id', ## 延期单id
            'task.entity_id as entity_id',          ## 实体id
        ];
        $abnormalEntityDelayConfirmList = model(DelayOrderTaskModel::class)
            ->alias('dot')
            ->join('delay_order_task_step_category_confirm dotsc on dotsc.delay_order_task_id = dot.id')
            ->join('task on task.id = dot.task_id')
            ->where([
                'dot.confirm_status' => [             ## 如果是yes或no,说明延期单拒绝或通过
                    'IN',
                    [
                        'producer_confirming',        ## confirm_status.producer_confirming 待 制片确认
                        'step_category_confirming'    ## confirm_status.step_category_confirming:待环节确认
                    ]
                ]])
            ->where(['task.id' => ['IN', $taskIds]])
            ->where(['task.plan_start_time' => ['BETWEEN', [$deliveryTime[0], $deliveryTime[1]]]])
            ->field($field)
            ->limit(1000)
            ->select();
        if (empty($abnormalEntityDelayConfirmList)) {
            return [];
        }
        return $abnormalEntityDelayConfirmList;
    }
}
